Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
In order to accept requests from Nightfall, a Webhook server must use a signing key to verify requests.
To access or generate your Webhook signing key, start by logging in to the Nightfall dashboard.
Select the Developer Platform > Manage API Keys using the navigation bar on the left side of the page. You will see the Webhook signing section:
Unlike the API Key, it is possible to reveal the signature via the "eye" icon furtherest to the left of the three icons displayed.
You may copy the current value to your clipboard with the "copy" icon in the center of the three icons displayed.
You may also regenerate the key with the circular arrow icon furthest to the right.
Use this value as shown in the code examples that are used in the following sections.
The Nightfall API supports the ability to send asynchronous notifications when findings are detected as part of a scan request.
The supported destinations for these notifications include external platforms, such as Slack, email, or url to a SIEM log collector as well as to a webhook server.
Nightfall issues notifications under the following scenarios:
to notify a client about the results of a . File scans themselves are always performed asynchronously because of complexity relating to text extraction and data volume.
to notify a client about results from a text scan request. Although results are already delivered synchronously in the response object, clients may configure the request to forward results to other platforms such a webhook, SIEM endpoint, or email through a
To create a webhook you will need to and then set up a
For more information on how webhooks and asynchronous notifications are used please see our guides on:
Policies allow customers to create templates for their most common workflows by unifying a set of Detection Rules with the actions to be taken when those rules are triggered, including:
automated actions such as redaction of findings
alerting through webhooks
Once defined, a Policy may be used in requests to the Nightfall API, such as calls to scan file uploads, though automated redactions are not available for uploaded files at this time.
To create a policy:
Log in to Nightfall.
Click Policies under the Configuration section.
Click + New Policy.
Select Developer APIs.
Select the Detection Rules to be included in the policy and click Next.
Configure a notification channel. Click to learn more about alert channel configuration. If you wish to setup a webhook alert channel, click for details.
Click Next.
(Optional) Enable the Redact Message toggle switch. This is an automated action that is triggered when sensitive data is found. The action automatically redacts sensitive data.
Click Next.
Enter a name for the policy.
(Optional) Enter a Description for the policy and click Next.
Verify the configurations and click Submit.
To configure Webhook as an alert channel:
Enable the Webhook Alert notification channel.
In the Configure Webhook URL field, enter the URL of the Webhook to which you wish to send notifications.
(Optional) Click Add Headers to add header key value pairs.
Click Validate.
Once validated, you can click Next to proceed.
Similarly, you can also use HTTP alerts channel.
The API expects an API Key to be passed via the Authorization: Bearer <key> HTTP header.
To create and manage API keys:
Log in to Nightfall.
Click Overview under the DEVELOPER APIS section.
Click Create key.
The Generate API Key window is displayed.
Enter a name for the API key and click Create.
The API key is generated and displayed. Click the copy button to copy the API key and store it in a. secure location. Once you click the Got it button, you cannot retrieve the API key again.
🚧Be Sure to Record the API Key's ValueFor security reasons, after closing the window, you will not be able to recover the key's value.
Once you close the window, the Your API Keys section will display your newly generated key, with the majority of the Key redacted.
You can click the Create Key button to create new keys (assuming your license allows you to generate additional keys). You can click the Delete icon against an API key to delete the key.
The Nightfall API uses API keys to authenticate requests. You can create and view your API keys in the Nightfall app on the page.
Your API keys carry many privileges, so be sure to keep them secure. Do not share your secret API keys in publicly accessible areas such as GitHub, client-side code, or anywhere else that would compromise their secrecy. If you believe one of your API Keys has been compromised, you should delete it through the Dashboard.
All API requests must be made over HTTPS.
Calls made over plain HTTP will fail.
API requests without authentication will fail.
Protected health information (PHI), also referred to as personal health information, describes a patient's medical history — including ailments, various treatments, and outcomes. PHI may include:
demographic information
test and laboratory results
mental health conditions
insurance information
The Health Insurance Portability and Accountability Act (HIPAA) of 1996 is the primary law that oversees the use of, access to, and disclosure of PHI in the United States. HIPAA lists 18 different personal information identifiers (PII) that, when paired with health information, become PHI. In order to more accurately detect potential PHI, Nightfall has introduced specific new detectors that allow for specialized combinations.
These HIPAA PII and PHI-specific detectors intelligently aggregate Nightfall's built-in detector to ensure compliance with governing law. For example, finding a patient's name in a document or message is not considered HIPAA PII as it does not uniquely identify an individual, many people can share the same name. However, the information would be considered HIPAA PII if the patient's name and address were in the same message.
Specific PHI and HIPAA PII can be detected with greater confidence, especially as they relate to specific medical codes or terms in association with specific logical combinations of other PII. For instance when the patient's name and date of birth or a person's name and street address or any of a set of particular PII (phone number email, SSN, etc) it would be considered HIPAA PII.
If the combined detectors all match with a confidence of "Very Likely" it would match our "HIPAA PII Very Likely" Detection Rule. Otherwise if these detectors match with a confidence of "Likely" it would match our "HIPAA PII Likely" Detection Rule.
Alternatively when any of the above PII options are found in conjunction with a specific set of medical related codes or terms (IDC Codes, FDA Drug Names or Codes, Procedures), that finding could be flagged as PHI.
When all the detectors within these PHI Detection Rules make findings that have a confidence of "Very Likely," that would match our "PHI Very Likely" Detection Rule, while if some are all are met with a confidence of "Likely" that would match our "PHI Likely" Detection Rule.
Our PHI Detectors may be used just like other Detectors with or .
Before you use the scan endpoint, there are a number of actions to do within the Nightfall dashboard to get your environment set up properly.
See to see how to create the necessary Authentication token for making API calls.
See for how to define your own custom logic for detecting sensitive data
See for how to aggregate Detectors for use in the scan endpoint
See for how to set up common workflows that combine your Detection Rules with remediation actions such as alerting.
Welcome to the amazing world of the Nightfall Developer APIs (formerly known as Firewall for AI). Here you can find all the information about Nightfall's APIs, and SDKs, and also usage examples of these APIs and SDKs.
The following sample datasets can be used to test Nightfall's advanced AI-based detection capabilities.
This data has been fully de-identified and can be used to test any data loss prevention (DLP) platform.






Welcome to Nightfall's Firewall for AI Developers Scan and Workflow APIs documentation. This documentation helps developers leverage Nightfall AI's industry-leading detection engine to identify and protect sensitive customer and corporate data anywhere. It prevents unauthorized access and data breaches and allows you to focus on innovation.
Scan prompts, text, documents, spreadsheets, logs, zips, JSON, images, etc., for PII, PHI, PCI, banking information, API keys, passwords, and network information with the highest accuracy and lightning-fast response times. Redact sensitive findings with customizable formatting.
Leverage the full potential of the Nightfall console application through our Workflow APIs. Customize your SIEM workflows and reporting, take actions, update support tickets, alert users, search violations, annotate findings, create reports, and more.
AI-Powered Identification: Utilize advanced AI models to detect and prevent security threats in real-time.
Comprehensive Sensitive Data Detection: Identify PII, PHI, PCI, banking information, API keys, passwords, and network information across various formats including text, documents, spreadsheets, logs, zips, and images.
Customizable Redaction: Tailor data protection to your needs with fully customizable redaction for each sensitive entity type.
Flexible Detectors: Leverage Nightfall’s comprehensive list of machine learning-based detectors, customize them, or create your own with specialized logic.
High Accuracy and Performance: Achieve precision and recall rates of 95% or higher, handle over 1K requests per second, and experience latency of less than 100 ms.
Seamless Integration: Easily integrate with your existing AI development and data engineering tools for smooth and efficient operation.
You can leverage Nightfall’s machine learning-based detectors or create your own detectors with customized logic to scan third-party apps, internal services, and data silos to identify instances of potentially sensitive types of data such as:
Personally Identifiable Information (PII) including Social Security Numbers, passport numbers, email addresses, or date of birth
Protected Health Information (PHI) such as insurance claim numbers or ICD10 codes
Financial information like credit card numbers or bank routing numbers
Secrets such as API and cryptographic Keys, database connection strings, passwords, etc.
Network information such as IP Address or MAC Address
Key features of Nightfall’s detection engine include:
Defining minimum confidence thresholds and minimum finding counts on detectors to reduce the chance of false positives.
Specifying context rules and exclusion rules on detectors to fine-tune their accuracy to better suit your use cases.
Choosing which detectors are triggered for each policy.
The Nightfall API consumes arbitrary data as input either as strings or as files and allows you to use any combination of detectors to return a collection of “findings" objects.
The detectors may be referenced in an API call or defined as part of the payload to an API call.
The findings display the relevant detector, the likelihood of a match, and the location within the given data where the matched token occurred (not only in terms bytes — there is support for tabular and JSON data as well).
You can take protective action on sensitive text by redacting, substituting, or encrypting it with the API. You may also set up webhooks to receive asynchronous notifications when findings are detected.
The Nightfall API is RESTful and uses JSON for its payloads. Our API is designed to have predictable, resource-oriented URLs for each endpoint and uses HTTP response codes to indicate any API errors.
You may test out the API through the interactive reference documentation.
The following guide will walk you through getting started and describe the API functionality in more detail. If you want to execute an API call immediately, see our Quickstart guide to see how to obtain an API Key and make a simple scan request.
After that, you can learn about Nightfall with our Key Concepts section, which will also help you get set up with Nightfall.
If you’re looking for more ideas about best to leverage Nightfall’s functionality, see our Use Cases guide.
We have created numerous tutorials and example implementations that demonstrate how to implement DLP for a variety of platforms (including OpenAI, LangChang, Amazon, Datadog, and Elasticsearch) and handle various scenarios (such as detecting sensitive data in GenAI prompts or detecting PII on your machine in real-time).
We also have several language-specific SDKs to get you up and running in Java, Python, Go, Node.js, and Ruby.
You can also quickly test out Nightfall detectors or your custom Detection Rules in the Nightfall Playground. Please also consult our Detector Glossary to see the variety of built-in detectors that Nightfall offers.
The Firewall for AI Overview page allows you to create API keys and manage Detectors and Detection Rules through a straightforward user interface. Log in here to access the Dashboard, or sign up to create a free account.
For frequently asked questions, feedback, and other help, please contact Nightfall support at [email protected]. We also host Nightfall Developer Office Hours on Wednesdays at 12pm PT to help answer questions, talk through any ideas, and chat about data security. We would love to see you there!
The scan endpoint allows you to apply Policies and Detection Rules to a list of text strings provided as a payload.
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER"
}
],
"name": "My Match Rule",
"logicalOp": "ANY"
}
]
},
"payload": [
"The customer social security number is 458-02-6124",
"No PII in this string"
]
}You may use Pre-Configured Detection Rules or Create Inline Detection Rules
Text scanning supports the use of Exclusion Rules, Context Rules, and Redaction as well as other Scanning Features.
For scanning files, see Scanning Files.
Note that you must generate an API key to send requests to the Nightfall API.
Nightfall offers many useful features beyond its detectors, including:
The ability to use Context Rules and Exclusion Rules to narrow the scope of matches.
The ability to create Redactions in a way that is highly configurable so that sensitive data is appropriately obfuscated.
The ability to create Policies that determine how leaks of sensitive information should be mitigated (i.e. through alerts sent to email or Slack).
Nightfall’s file scan API allows a user to upload a file in chunks, then to scan it with Detection Rules once the upload is complete.
The scan will then be processed asynchronously before sending the results to the webhook URL that is provided along with your Detection Rules.
The following sequence diagram illustrates the full process for scanning a binary file with Nightfall.
For a detailed walkthrough of the API calls necessary to upload and scan a file and full script that shows the entire process, see Uploading and Scanning Files.
In order to utilize the File Scanning API you need the following:
An active API Key authorized for file scanning passed via the header Authorization: Bearer <key> — see Authentication and Security
A Nightfall Detection Policy associated with a webhook URL
A web server configured to listen for file scanning results (detailed information to follow)
File scanning also support Nightfall's functionality for Using Exclusion Rules and Using Context Rules as part of your scan requests.
This section describes the terms you will need to know when using the API.
Detectors provide the logic to find potentially sensitive pieces of data.
When this logic detects such data, the Detector is considered "triggered."
Nightfall's has numerous pre-built Detectors that are trained via machine learning. Detectors may also be defined with regular expressions or dictionaries. Their accuracy may be further refined with exclusion rules and context rules. Whether a Detector is triggered may be controlled by a minimum confidence threshold per Detector and minimum number of findings per Detector as set on a Detection Rule.
The built-in set of Detectors cover a number of different categories of data, including:
Standard PII (e.g. social security number, driver's license number, ID card image)
PCI (Credit Card Number, credit card image)
Healthcare (e.g. PHI, US Medicare Beneficiary Number)
Finance - Banking (e.g. SWIFT code, IBAN code, US bank routing number)
Network (e.g. an IP Address)
The full set is enumerated in the .
Nightfall also supports and word lists for any custom detectors that you may want to implement.
Over time, we've aggregated the following , which you're welcome to select from to save you some time. Please note that a regular expression is an established yet limited method that searches for pre-defined patterns, so your mileage may vary.
You can test regular expressions .
You can input custom detectors in two ways: directly in the Nightfall by navigating to Detectors → New Detector → Regular expression, or
An exclusion rule is a regular expression or word list that will be used once a Detector is triggered by its primary expression or word list to eliminate false positives.
For instance, you may have a Detector designed to detect phone numbers. However, you may have a particular set of phone numbers that you use for testing purposes that are known not to be valid (e.g. they start with the prefix 555) and this should be ignored. Adding an exclusion rule would allow you to prevent those matches from being returned by the API.
See:
Context Rules are additional matching expressions for a Detector that may be used to adjust the confidence score of a match.
You may provide a regular expression and the number of leading or trailing characters within which a match of that expression must occur in order to adjust the confidence level to a particular level.
For instance, if you found a sequence that appeared to be a social security number based on its length or formatting, you might boost the confidence score if it was preceded by the text like “SSN” or “Social Security Number.”
You may request that a sequence of bytes of a given length be provided from before and after the text that triggers a Detection Rule.
This information can help you better understand whether or not something is an actual violation by observing the circumstances within which the detected text was found.
You are limited to a maximum of 40 bytes of this context text preceding and trailing the match for a total of 80 bytes overall.
See:
Detection Rules are aggregations of Detectors that are assigned a minimum confidence level. The identifiers of Detection Rules are used as a parameter to the API.
You may create Detection Rules as described in the section and use their identifier as part of API calls to scan content.
Alternatively you may specify Detection Rules in each API call, as described in the scan method documentation below.
A Detection Rule is composed of a list of Detectors with which you wish to scan each request payload, where any or all Detectors may be satisfied in order to trigger the rule. You can add up to 50 total Detectors with a limit of 30 regular expression type custom detectors.
Additionally, each Detector in the Detection Rule is assigned a “minimum confidence” level (see and a minimum number of findings to determine if the Detection Rule should be considered triggered.
Detection results will be returned with one of the following confidence values.
In practice, the API will only return detections assigned a POSSIBLE or higher confidence level.
VERY_LIKELY (recommended)
LIKELY
POSSIBLE
UNLIKELY
VERY_UNLIKELY
Learn more about what different confidence levels mean and how to choose the right minimum confidence level for your detection rule .
Policies allow you to create templates for the most common workflows by unifying a set of Detection Rules with the actions to be taken when those rules are triggered, including:
automated actions such as redaction of findings
alerting through webhooks
Once defined, a Policy may be used in requests to the Nightfall API, such as calls to scan file uploads, though automated redactions are not available for uploaded files at this time.
Using regex to identify long patterns in images can be challenging because OCR systems. In such cases, even Nightfall may not achieve 100% character-by-character accuracy. To improve results, you must introduce higher levels of flexibility into your regex patterns to accommodate common OCR inconsistencies. Here are some typical OCR challenges to keep in mind:
Spell-check noise: Spell-checking tools can add artifacts like red underlines, which may interfere with text recognition.
Character ambiguity:
The digit 0 may be misinterpreted as the letter O (or vice versa), depending on the font.
The character l (lowercase L) may be read as the digit 1.
The letter B may appear as the digit 8.
Underscore handling: An underscore (_) is sometimes interpreted as a space, particularly when spell-check artifacts are present.
Line wrapping: OCR may introduce unexpected newlines when text wraps across multiple lines.
Periods and punctuation: Spell-check artifacts or font issues may result in extraneous periods (.) or other punctuation being added to the output. En dash (–) and hyphens (-) may be interchanged.
For reference, OCR tools like Tesseract typically achieve 85-98% character accuracy for similar input, and our system operates within a similar range. Given this, tuning your regex to be more forgiving (e.g., allowing for optional characters or slight variations) can significantly improve detection rates.
Example Regex (original and loosened)
original: ATATT3xFfGF0[A-Za-z0-9=_\-]*[=A-Za-z0-9]{9}
loosened: ATATT[A-Za-z0-9_\-– @.\n=]*[A-Za-z0-9_\- @.\n]{7,11}
shortened the literal match prefix
excluded the the literal zero (0) from the prefix
added period (.) and newline () chars
relaxed the char length


As part of submitting a file scan request, the request payload must contain a reference to a webhook server URL defined as part of a policy defined inline.
When Nightfall prepares a file scan operation, it will issue a challenge to the webhook server to verify its legitimacy.
After the file scan has been processed asynchronously, the results will be delivered to the webhook.
For a file scan, your webhook will receive a request body that will be a JSON payload containing:
the upload UUID (uploadID)
a boolean indicating whether or not any data in the file matched the provided detection rules (findingsPresent)
a pre-signed S3 URL where the caller may fetch the findings for the scan (findingsURL). if there are no findings in the file, this field will be empty.
the date until which the findingsURL is valid (validUntil) formatted to RFC 3339. Results are valid for 24 hours after scan completion. The time will be in UTC.
the value you supplied for requestMetadata. Callers may opt to use this to help identify their input file upon receiving a webhook response. Maximum length 10 KB.
Below is an example of a payload sent to the webhook URL.
{
"findingsURL": "https://files.nightfall.ai/asdfasdf-asdf-asdf-asdf-asdfasdfasdf.json?Expires=1635135397&Signature=asdfasdfQ2qTmPFnS9uD5I3QGEqHY2KlsYv4S-WOeEEROj~~x6W2slP2GvPPgPlYs~lwdr-mtJjVFu4LtyDhdfYezC7B0ysfJytyMIyAFriVMqOGsRJXqoQfsg8Ckd2b6kRcyDZXJE25cW8zBS08lyVwMBCsGS0BKSin8uSuD7pQu3QAubT7p~MPkfc6PSXYIJREBr3q4-8c7UnrYOAiXfSW1AmFE47rr3Wxh2TpU3E-Fxu-6e3DKN4q6meACdgZb2KHZo3e-NK7ug9f8sxBp1YT0n5oiVuW4KXguIyXWN~aKEHMa6DzZ4cUJ61LmnMzGndc2sVKhii39FHwTsYog__&Key-Pair-Id=asdfOPZ1EKX0YC",
"validUntil": "2021-10-25T04:16:37.734633129Z",
"uploadID": "152848af-2ac9-4e0a-8563-2b82343d964a",
"findingsPresent": true,
"requestMetadata": "",
"errors": []
}If you follow the URL (before it expires) it will return a JSON representation of the findings similar to those returned by the Scan Plain Text endpoint.
In this example, we have uploaded a zip file with a python script (upload.py) and a README.md file. A Detector in our DetectionRule checks for the presence of the string http://localhost
{
"findings":[
{
"path":"fileupload/upload.py",
"detector":{
"id":"58861dee-b213-4dbc-97fa-a148acb8bd1a",
"name":"localhost url"
},
"finding":"http://localhost",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":105,
"end":121
},
"codepointRange":{
"start":105,
"end":121
},
"lineRange":{
"start":7,
"end":7
}
},
"beforeContext":"PLOAD_URL = getenv(\"FILE_UPLOAD_HOST\", \"",
"afterContext":":8080/v3\")\nNF_API_KEY = getenv(\"NF_API_K",
"matchedDetectionRuleUUIDs":[
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules":[
]
},
{
"path":"fileupload/README.md",
"detector":{
"id":"58861dee-b213-4dbc-97fa-a148acb8bd1a",
"name":"localhost url"
},
"finding":"http://localhost",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":570,
"end":586
},
"codepointRange":{
"start":570,
"end":586
},
"lineRange":{
"start":22,
"end":22
}
},
"beforeContext":"t the script will send the requests to `",
"afterContext":":8080`, but this can be overridden using",
"matchedDetectionRuleUUIDs":[
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules":[
]
},
{
"path":"fileupload/README.md",
"detector":{
"id":"58861dee-b213-4dbc-97fa-a148acb8bd1a",
"name":"localhost url"
},
"finding":"http://localhost",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":965,
"end":981
},
"codepointRange":{
"start":965,
"end":981
},
"lineRange":{
"start":26,
"end":26
}
},
"beforeContext":"ice deployment you want to connect to | ",
"afterContext":":8080 |\n| `NF_API_KEY` | the API Ke",
"matchedDetectionRuleUUIDs":[
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules":[
]
}
]
}An Exclusion Rule allows you to refine a Detector to make sure false positives are not surfaced by Nightfall.
For instance you may want to detect whether credit card numbers are being shared inappropriately in your organization. However, there may be cases where members of your QA are sharing test credit card numbers, which should not be considered a violation and should be ignored by Nightfall.
In the following example, we define a Detector with a regular expression to match credit cards.
We then add an exclusion for some known test credit cards.
curl --location --request POST 'https://api.nightfall.ai/v3/scan' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data-raw '{
"policy": {
"detectionRules": [
{
"detectors": [
{
"regex": {
"pattern": "(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))",
"isCaseSensitive": false
},
"exclusionRules": [
{
"wordList": {
"values": [
"4111111111111111",
"5105105105105100"
]
},
"exclusionType": "WORD_LIST",
"matchType": "FULL"
}
],
"minNumFindings": 1,
"minConfidence": "POSSIBLE",
"displayName": "Credit Card Reg Ex",
"detectorType": "REGEX"
}
],
"name": "Credit Card Detection Rule",
"logicalOp": "ALL"
}
]
},
"payload": [
"5105105105105100",
"4111111111111111",
"4012888888881881"
]
}'As the resulting payload shows, only the 3rd provided Credit Card number matches because the first two items in the payload are included in our ExclusionRules word list.
{
"findings":[
[
],
[
],
[
{
"finding":"4012888888881881",
"detector":{
"name":"Credit Card Reg Ex",
"uuid":"93024e88-e6de-4c84-8295-75157cdd1b52"
},
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":0,
"end":16
},
"codepointRange":{
"start":0,
"end":16
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"Credit Card Detection Rule"
]
}
]
],
"redactedPayload":[
"",
"",
""
]
}This section consists of use case tutorials for various scenarios of Firewall for AI. The tutorials explained in this section are as follows.
You can start scanning for sensitive data in just a few minutes. Our developer-friendly API and comprehensive documentation make it easy to integrate Firewall for AI into your application. Follow our Quickstart guide at this link for step-by-step instructions on setting up the API, configuring detectors, and making your first API call.
Absolutely! In addition to the pre-built detectors, Firewall for AI allows you to create custom detectors tailored to your specific requirements. You can either fine-tune one of our pre-configured detection rules or build your own detector from scratch using our intuitive API. Nightfall supports many traditional detector types such as regular expressions, exact data matching, and word list/dictionaries. Check out our dedicated guide on creating custom detectors for more information.
Firewall for AI offers a rich set of pre-built detectors that can identify many different types of sensitive data, including personally identifiable information (PII), payment card industry data (PCI), protected health information (PHI), secrets, and credentials. These detectors are powered by advanced machine learning models and can be easily integrated into your application with just a few lines of code. Refer to our detector glossary at docs.nightfall.ai/docs/detector-glossary for a complete list of available detectors.
At Nightfall, data security and privacy are our top priorities. We have implemented stringent security measures to protect your sensitive data at every stage of the scanning process. All data transmitted to our API is encrypted in transit using industry-standard protocols. We adhere to best practices for secure coding, undergo regular security audits, and maintain compliance with relevant security standards. Visit our security and compliance page at nightfall.ai/security for more details on our commitment to data protection.
Nightfall supports Detectors that will scan for file names, file types, and file finger prints.
In addition to scanning the content of files, you may configure the Detectors to scan file names as well.
This is done through the “scope” attribute of a Detector.
The scope attribute allows you to scan either within file contents, the file name, or both the file contents and file name.
File extensions can be scanned for by creating a Regular Expression type custom Detector with a scope to scan only file names ("File") or both the content and file name ("ContentAndFile"), as shown in the example request below.
curl --request POST \
--url https://api.nightfall.ai/v3/upload/<fileid>/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-<yourNightfallKey> \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"regex": {
"pattern": "*\.txt",
"isCaseSensitive": false
},
"detectorType": "REGEX",
"scope": "ContentAndFile"
}
],
"name": "File Name Detector",
"logicalOp": "ANY"
}
]
}
}
In addition to scanning based on file name, you may also use a File Type Detector which allows you to scan for files based on their mime-type.
Note that confidence sensitivity does not apply to file names. Sensitive findings will always be reported on.
Nightfall’s File Type detection allows you to implement compliance policies that detect and alert you when particular file types that are not allowed in a given location are discovered.
This functionality is implemented by creating a specific Detector called a “File Type Detector”
To create a File Type Detector, select “Detectors” from the left hand navigation and click the button labeled “+New Detector” in the upper right hand corner. From there a drop down list of Detector types will be displayed which will include the “File Type” Detector type.
You will then select one or more file types for which to scan by selecting from a list of mime-types
You can either scroll through the list of mime-types in the select box or you may type in a portion of the mime-type and the contents of the select box will be filtered to match your input.
Nightfall supports detection for a wide variety of mime-types. See the Internet Assigned Numbers Authority’s (IANA) website for a definitive list of mime-types. Note however that Nightfall does not support the detection of audio and video related mime-types.
Detection of file types is done based on the file contents, not its extension. However, you can create Detectors that scan file names by setting the scope attribute.
File Type Detectors vary from other Nightfall Detectors in that the attributes of scope and confidence are not relevant to File Type Detectors
Once you have added all the mime-types you wish to scan for, save your new Detector. You may then add your new Detector to Detection Rules and Policies.
Nightfall allows you to discover the location of specific files that you have deemed sensitive and want to avoid sharing.
This discovery is done through document fingerprinting. Fingerprinting is the process of algorithmically creating a unique identifier for a file by mapping the data of the document to a signature that can be recalled quickly. This allows the file to be identified in a manner akin to how human fingerprints uniquely identify individual people.
This functionality is achieved in Nightfall by creating a specific Detector type called a File Fingerprint Detector.
The Fingerprint Detector allows you to create a fingerprint for one more files (a sort “handful” of fingerprints, if you would).
To create a Fingerprint Detector, select “Detectors” from the left hand navigation and click the button labeled “+New Detector” in the upper right hand corner. From there a drop down list of Detector types will be displayed which will include the “Fingerprint” Detector type.
When you create a File Fingerprint Detector you can upload up to 50 files that need to be fingerprinted. The file size limit is 25MB.
Once the fingerprint is generated, the actual content of the file is discarded so no sensitive content is stored on Nightfall’s system.
These Detectors may only be created through the console.
You can not update Fingerprint Detectors, so any modification to the original file or underlying requires that you create a brand new Fingerprint Detector.
You may then treat the Fingerprint detector like any other Detector and incorporate it into a Detection Rule using its unique Detector identifier.
You may incorporate these Detectors into Policies that will alert you whenever files that match the fingerprint are detected.
APIs to monitor and manager integrations
You can use the posture management APIs to search posture events, fetch posture events and also event details. Additionally, you can also view details of the user (actor) whose actions triggered an event, and details of the asset that triggered an event.
Firewall for AI DLP APIs enables developers to write custom code to sanitize data anywhere–RAG data sets, analytics data stores, data pipelines, and unsupported SaaS applications.
Nightfall has the ability to send alerts when a violation is detected.
Policies for alerting may be configured through the Nightfall app user interface or they may be set up . Policies that are configured under Developer Platform > Overview > Policies may be used in the API by referencing their Policy UUID.
The way that an alert notification presents itself depends on the platform in question.
For example, notifications sent to Slack will appear as formatted messages sent by the Nightfall Alerts Bot. Other destinations such as email, SIEM url, and webhooks, will present the information as JSON objects.
In the case of webhooks, detailed information about the finding will be sent. For other destinations, sensitive information is redacted.
In order to use asynchronous notifications with Slack, you must install the Nightfall Alerts plugin from the Slack Marketplace.
See our end user documentation on installing for more details.
Once you have authenticated Nightfall to your Slack workspace, you can provide any public channel name (e.g. #general) as part of a request to the Nightfall API.
To send notifications to a private channel, a member of the channel should invite the Nightfall bot to the specific private channel and allow channel access to the bot.
Follow the steps below to invite Nightfall Alerts bot to a private channel:
Go to the Slack channel in question
Type /invite @Nightfall Alerts as a message
Press 'Enter' (you should see a message that Nightfall Alerts has now joined the channel)
If any findings are detected as part of that request, then the Nightfall Alerts bot will send a message to the channel you configured. Conversely, if there are no findings in the request payload, then Nightfall will not send an alert message.
Documentation TBD
Email is unauthenticated, so you can get started using Nightfall to send email alerts without any initial setup work.
Nightfall will send an email to the provided address only if findings were detected as part of the request. The findings themselves will be attached in a JSON file.
You may send your alerts to a designated url, such as an endpoint hosted by SIEM software for log collection.
In addition to the url, you may provide headers, either for security or logging purposes.
See in our end user guide or our for more details.
You may use a webhook server to programmatically handle a finding, allowing you to create your own custom workflows with your own or 3rd party systems.
Nightfall will always send an alert to the client's webhook server if it is provided as part of an API request, even if the scan request yielded no findings.
See for more details.
The request body sent by Nightfall is JSON, and uses the schemas in the section documented below.
Since file scans can produce a large number of results, findings are not transmitted directly in the notification that Nightfall sends. The notification object looks like the following:
The requestMetadata field contains arbitrary contents provided by the client at request time, and can be used by the client to correlate this response to the original request.
The value of the findingsURL field is a pre-signed URL, which means anyone with the link can download the file. Therefore, this URL itself should be treated as sensitive and must not be leaked. The object stored at this URL is a JSON file containing a single key findings containing a list of all data detected from the request. The schema for the finding object inside the list is shared between the text-based and file-based API endpoints.
The payload that is forwarded on behalf of text scanning requests is identical to the response body that is synchronously returned to the client. Refer to the for more details on this payload.
Learn how to set up a server to handle results of file scans and alerts sent based on policy alert configurations.
Nightfall will send a POST request with a JSON payload with a single field challenge containing randomly-generated bytes when it sends a message to a user-provided webhook address. This is to ensure that the caller owns the server.
In order to authenticate your webhook server to Nightfall, you must reply with (1) a 200 HTTP Status Code, and (2) a plaintext request body containing only the value of the challenge key.
If Nightfall receives the expected value back, then the file scan operation will proceed; otherwise it will be aborted.
When a server responds successfully to a challenge request, the validity of that URL will be cached for up to 24 hours, after which it will need to be validated again.
If the webhook cannot be reached, you will receive an error with the code "40012" and the description "Webhook URL validation failed" when you initiate the scan.
If the webhook challenge fails, you will receive an error with the code "42201" and the description "Webhook returned incorrect challenge response" when you initiate the scan.
When a customer signs up for the developer platform, Nightfall automatically generates a unique for them.
This secret is used to sign requests to the customer's configured webhook URL.
The signing secret should never be stored in plaintext, as a leak compromises the authenticity of webhook requests.
If you has any concerns that their signing secret may have leaked, you can request rotation at any time by reaching out to Nightfall Customer Success.
For security purposes, the webhook includes a signature header containing an HMAC-SHA256 digital signature that customers may use to authenticate the client.
In order to authenticate requests to the webhook URL, customers may use the following algorithm:
Check for the presence of the headers X-Nightfall-Signature and X-Nightfall-Timestamp. If these headers are not both present, discard the request.
Read the entire request body into a string body.
Verify that the value in the X-Nightfall-Timestamp header (the POSIX time in seconds) occurred recently. This is to protect against replay attacks, so a threshold on the order of magnitude of minutes should be reasonable. If a request occurred too far in the past, it should be discarded.
Concatenate the timestamp and body with a colon delimiter, i.e. timestamp:body.
Compute the HMAC SHA-256 hash of the payload from the previous step, using your unique signing secret as the key. Encode this computed value in hex.
Compare the value of the X-Nightfall-Signature header to the value computed in the previous step. If the values match, authentication is successful, and processing should proceed. Otherwise, the request must be discarded.
The snippet below shows how you might implement this authentication validation in Python:
An example implementation of a simple webhook server is below.
You can test your webhook with a tool such as which allows you expose a web server running on your local machine to the internet.
In the above example, the webhook server is running on port 8075. To route ngrok requests to this server, once you run the python script (having installed the necessary dependencies such getenv and Flask), you would run ngrok as follow:
./ngrok http 8075
See the section on for details about the json payloads for the different messages sent to webhook servers.
File scans of Microsoft Office, Apache parquet, csv, and tab separated files will provide additional properties to locate findings within the document beyond the standard byteRange, codepointRange, and lineRange properties.
Findings will contain a columnRange and a rowRange that will allow you to identify the specific row and column within the tabular data wherein the finding is present.
This functionality is applicable to the following mime types:
text/csv
text/tab-separated-values
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-excel
data files are also accepted.
Below is a sample match of a spreadsheet containing dummy PII where a SSN was detected in the 2nd column and 55th row.
Nightfall provides special handling for archives of GitHub repositories.
Nightfall will scan the repository history to discover findings in particular checkin, returning the hash for the checkin.
In order to scan the repository, you will need to create a clone, i.e.
git clone https://github.com/nightfallai/nightfall-go-sdk.git
This creates a clone of the Nightfall go SDK.
You will then need to create an archive that can be uploaded using Nightfall's file scanning sequence.
zip -r directory.zip directory
Note that in order to work, the hidden directory .github must be included in the archive.
When you initiate the with this file, you will receive scan results that contain the commitHash property filled in.
Using the Nightfall go SDK archive created above, a simple example would be to scan for URLs (i.e. strings starting with http:// or https://), which will send results such as the following:
Sensitive Data in GitHub Repositories
If the finding in a GitHub repository is considered to be sensitive, it should be considered compromised and appropriate mitigation steps (i.e. secrets should be rotated).
To retrieve the specific checkout, you will need to clone the repository, i.e.
git clone https://github.com/nightfallai/nightfall-go-sdk.git
You can then checkout the specific commit using the commit hash returned by Nightfall.
Note that you are in a when working with this sort of check out of a repository.
You can use the surrounding context of a match to help determine how likely it is that your potential match should actually be considered as a match by adjusting its confidence rating.
You can also tell the Detection Rule to return a portion of the surrounding context for manual review.
In the following example, in addition to providing a regular expression to match Social Security Numbers, we also look to see if someone has written the text “SSN” before and after the match, which might be a label indicating it is indeed a social security number. In which case, we change our confidence score to “VERY_LIKELY.” We then provide two possible matches in our payload, the first of which contains the string “SSN”.
In the results, you can see the confidence for the first finding in the payload has been set to VERY_LIKELY while the second item is only LIKELY.
While using Nightfall's Scan API, you may encounter some of the common errors outlined below. Try following the provided troubleshooting steps.
If problems persist, please for further assistance.
The following error codes are returned as part of a standard HTTP response.
The Nightfall Developer Playground () is a sample app that you may use to test out API functionality before writing any code.
Our playground environment allows you to:
Test Detectors and Detection Rules. Here are some .
Generate sample data for DLP testing.
Explore a sample app built on our APIs
We offer a free tier that allows you to sign up and start using Firewall for AI with zero upfront costs or commitments. This tier provides a generous data scanning capacity and access to all the core features.
We offer enterprise pricing plans for advanced requirements such as higher data volumes, custom rate limits, and dedicated support.
Contact our team at [email protected] or via the contact form on our website to discuss your specific needs and get a tailored pricing quote.
You can use the exfiltration APIs to search exfiltration events, fetch exfiltration events and also event details. Additionally, you can also view details of the user (actor) whose actions triggered an event, and details of the asset that triggered an event.
Don't hesitate to get in touch with us directly via email at or through the c on our website.
We host on Wednesdays at 12 pm PT to help answer questions, talk through any ideas, and chat about data security. We would love to see you there!
Leverage our software development kits (SDKs) to enable easier, faster, and more stable engagement with the Nightfall APIs. Nightfall has a growing library of language specific SDKs including for:
If there is a language-specific SDK that you would find valuable but is not here, please don't hesitate to reach out to .
Yes, you can test out the detection engine, including 70+ pre-built detectors without writing any code or having to sign up in our .
curl --location --request POST 'https://api.nightfall.ai/v3/scan' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data-raw '{
"policy": {
"detectionRules": [
{
"detectors": [
{
"regex": {
"isCaseSensitive": false,
"pattern": "\\d{3}-\\d{2}-\\d{4}"
},
"contextRules": [
{
"regex": {
"pattern": "SSN",
"isCaseSensitive": false
},
"proximity": {
"windowBefore": 20,
"windowAfter": 20
},
"confidenceAdjustment": {
"fixedConfidence": "VERY_LIKELY"
}
}
],
"minNumFindings": 1,
"minConfidence": "POSSIBLE",
"detectorType": "REGEX",
"displayName": "SSN Match Detector"
}
],
"name": "SSN Match Detection Rule",
"logicalOp": "ALL"
}
],
"contextBytes": 20
},
"payload": [
"My SSN is 555-55-5555",
"Here it is : 555-55-5555"
]
}
'{
"findings":[
[
{
"finding":"555-55-5555",
"beforeContext":"My SSN is ",
"detector":{
"name":"SSN Match Detector",
"uuid":"6131f41c-dbdd-47a9-8c6f-1819c9baf388"
},
"confidence":"VERY_LIKELY",
"location":{
"byteRange":{
"start":10,
"end":21
},
"codepointRange":{
"start":10,
"end":21
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"SSN Match Detection Rule"
]
}
],
[
{
"finding":"555-55-5555",
"beforeContext":"Here it is : ",
"detector":{
"name":"SSN Match Detector",
"uuid":"6131f41c-dbdd-47a9-8c6f-1819c9baf388"
},
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":13,
"end":24
},
"codepointRange":{
"start":13,
"end":24
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"SSN Match Detection Rule"
]
}
]
],
"redactedPayload":[
"",
""
]
}{
"errors": [],
"findingsPresent": true,
"findingsURL": "https://files.nightfall.ai/877442c5-1573-4637-a223-595bf620e3e5.json?Expires=1645722381&Signature=C-kQbtonFAPXfooGcm0dYgbsn9jfGu~vGSv5yK5j1z2f7aAhk0WuaL4bISUwx5MZkQmPVFgeyMwemvEoI8aI11lPA-ORsX5LtRdGJBOma4sPVl~9f9qBPKE2VSrdGDmT4EpBLc8ewUtKrLm2xE-0BzW~5PdLSvZ~NQxtB7OMBaYm7h~y2NSUZfpqzdzENyKhyHx5QxH2PJvxeN5IvMXqNUrKyZsxviSYY6kDNAiGExS-u6PmKKS1GhXOaFLdJSRjgtFhUxDLyWl~xTYR-lJol5UTgtcuYU8AaJ3xVTF1-1JYRlioRlaf9shAvme4djFyg8k~zOB8bYgzBeaRqSjeWA__&Key-Pair-Id=K3RYMP51FKX5HX",
"requestMetadata": "some data",
"uploadID": "877442c5-1573-4637-a223-595bf620e3e5",
"validUntil": "2022-02-24T17:06:21.412377682Z"
}{
"findings": [
{
"detector": {
"id": "74d1315e-c0c3-4ef5-8b1e-6cf98664a854"
},
"finding": "4242-4242-4242-4242",
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 146,
"end": 165
},
"codepointRange": {
"start": 146,
"end": 165
},
"lineRange": {
"start": 3,
"end": 3
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"beforeContext": "nd HIPAA Defined PII\nHIPAA HIPAA hooray\n",
"afterContext": " is my credit card number\n\n",
"matchedDetectionRuleUUIDs": ["7bd6166a-b9af-4069-847d-487a88788122"],
"matchedDetectionRules": []
}
]
}{
"findings": [
[
{
"finding": "4242-4242-4242-4242",
"beforeContext": "hello world cc ",
"detector": {
"name": "Credit card number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 15,
"end": 34
},
"codepointRange": {
"start": 15,
"end": 34
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"matchedDetectionRuleUUIDs": [
"42efe36c-6479-412a-9049-fd8cdf895ced"
],
"matchedDetectionRules": []
}
]
],
"redactedPayload": [""]
}{"challenge": "z78woE1uDFu7tPrPvEBV"}from datetime import datetime, timedelta
import hmac
import hashlib
from flask import request
SIGNING_SECRET = "super-secret"
given_signature = request.headers.get('X-Nightfall-Signature')
req_timestamp = request.headers.get('X-Nightfall-Timestamp')
now = datetime.now()
if now-timedelta(minutes=5) <= datetime.fromtimestamp(int(req_timestamp)) <= now:
raise Exception("could not validate timestamp is within the last few minutes")
computed_signature = hmac.new(
SIGNING_SECRET.encode(),
msg=F"{req_timestamp}:{request.get_data(as_text=True)}".encode(),
digestmod=hashlib.sha256
).hexdigest().lower()
if computed_signature != given_signature:
raise Exception("could not validate signature of inbound request!")import hmac
import hashlib
from os import getenv, path, mkdir
from flask import Flask, request
import requests
app = Flask(__name__)
output_dir = "findings"
SIGNING_SECRET = getenv("NF_SIGNING_SECRET")
@app.route("/", methods=['POST'])
def hello():
content = request.get_json(silent=True)
challenge = content.get("challenge")
if challenge:
return challenge
else:
verify_signature()
print(F"Received request metadata: {content['requestMetadata']}")
print(F"Received errors: {content['errors']}")
if not content["findingsPresent"]:
print(F"No findings for {content['uploadID']}")
return "", 200
print(F"S3 findings valid until {content['validUntil']}")
response = requests.get(content["findingsURL"])
save_findings(content["uploadID"], response.text)
return "", 200
def verify_signature():
if SIGNING_SECRET is None:
return
given_signature = request.headers.get('X-Nightfall-Signature')
nonce = request.headers.get('X-Nightfall-Timestamp')
computed_signature = hmac.new(
SIGNING_SECRET.encode(),
msg=F"{nonce}:{request.get_data(as_text=True)}".encode(),
digestmod=hashlib.sha256
).hexdigest().lower()
if computed_signature != given_signature:
raise Exception("could not validate signature of inbound request!")
def save_findings(scan_id, finding_json):
if not path.isdir(output_dir):
mkdir(output_dir)
output_path = path.join(output_dir, f"{scan_id}.json")
with open(output_path, "w+") as out_file:
out_file.write(finding_json)
print(F"Findings for {scan_id} written to {output_path}")
if __name__ == "__main__":
app.run(port=8075){
"findings":[
{
"path":"Sheet1 (5)",
"detector":{
"id":"e30d9a87-f6c7-46b9-a8f4-16547901e069",
"name":"US social security number (SSN)",
"version":1
},
"finding":"624-84-9182",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":2505,
"end":2516
},
"codepointRange":{
"start":2452,
"end":2463
},
"lineRange":{
"start":55,
"end":55
},
"rowRange":{
"start":55,
"end":55
},
"columnRange":{
"start":2,
"end":2
},
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules":[
]
},
...{
"findings":[
{
"path":"f607a067..53e59684/nightfall.go",
"detector":{
"id":"6123060e-2d9f-4f35-a7a1-743379ea5616",
"name":"URL"
},
"finding":"https://api.nightfall.ai/\"",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":142,
"end":168
},
"codepointRange":{
"start":142,
"end":168
},
"lineRange":{
"start":16,
"end":16
},
"rowRange":{
"start":0,
"end":0
},
"columnRange":{
"start":0,
"end":0
},
"commitHash":"53e59684d9778ceb0f0ed6a4b949c464c24d35ce"
},
"beforeContext":"tp\"\n\t\"os\"\n\t\"time\"\n)\n\nconst (\n\tAPIURL = \"",
"afterContext":"\n\n\tDefaultFileUploadConcurrency = 1\n\tDef",
"matchedDetectionRuleUUIDs":[
"cda0367f-aa75-4d6a-904f-0311209b3383"
],
"matchedDetectionRules":[
]
},
...cd nightfall-go-sdk
git checkout 53e59684d9778ceb0f0ed6a4b949c464c24d35ce400
Bad Request
This error most often occurs when there is something syntactically incorrect in the body of your request. Check your request format and try again. For example, this error could occur if the request body size is greater than 500 KB, or if the number of items to scan in the payload exceeds 50,000.
401
Unauthorized
You may be using an incorrect API key or calling the wrong endpoint.
422
Unprocessable Entity
You may be using an invalid or unrecognized detector set. You may also have exceeded the maximum allowable payload size; try spreading your payload across multiple requests.
429
Too Many Requests or Quota Exceeded
Either your monthly request limit has been exceeded, or you have exceeded the allowed rate limit. Consider upgrading to a higher volume plan, or wait several moments to retry the requests.
500
Internal Server Error
Wait a few moments and try again. If the problem persists, Nightfall may be experiencing an outage.




The Document will guide you in making your first API request.
This page will get you up and running with the Nightfall API so you can start scanning for sensitive data.
The Nightfall API requires a valid API key to authenticate your API requests.
You can create API keys in the Dashboard.
Learn more about Authentication and Security.
Below is an example request to the scan endpoint.
To run this example yourself, replace the API key (NF-rEpLaCe...) with the one you created in the dashboard or set it as the environment variable NIGHTFALL_API_KEY as necessary.
The cURL example may be run from the command line without any additional installation. To run the Python example, you will need to download the corresponding SDK.
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"minNumFindings": 1,
"minConfidence": "VERY_LIKELY",
"displayName": "US Social Security Number",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER"
},
{
"redactionConfig": {
"maskConfig": {
"charsToIgnore": [
"-"
],
"maskingChar": "X",
"maskRightToLeft":true,
"numCharsToLeaveUnMasked":4
}
},
"minNumFindings": 1,
"minConfidence": "VERY_LIKELY",
"displayName": "Credit Card Number",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "CREDIT_CARD_NUMBER"
}
],
"name": "My Match Rule",
"logicalOp": "ANY"
}
]
},
"payload": [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 5310-2768-6832-9293"
]
}
'// By default, the client reads your API key from the environment variable NIGHTFALL_API_KEY
const nfClient = new Nightfall();
const payload = [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 5310-2768-6832-9293"
];
const policy = {
"detectionRules": [
{
"detectors": [
{
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER"
},
{
"redactionConfig": {
"maskConfig": {
"charsToIgnore": [
"-"
],
"maskingChar": "#"
}
},
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "Credit Card Number",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "CREDIT_CARD_NUMBER"
}
],
"name": "My Match Rule",
"logicalOp": "ANY"
}
]
};
const response = await nfClient.scanText(payload, policy);
if (response.isError) {
console.log(response.getError());
} else {
response.data.findings.forEach((finding) => {
if (finding.length > 0) {
finding.forEach((result) => {
console.log(`Finding: ${result.finding}, Confidence: ${result.confidence}`);
});
}
});
}// Some code>>> from nightfall import Confidence, DetectionRule, Detector, Nightfall
>>> # By default, the client reads the API key from the environment variable NIGHTFALL_API_KEY
>>> nightfall = Nightfall()
>>> # A rule contains a set of detectors to scan with
>>> cc = Detector(min_confidence=Confidence.LIKELY, nightfall_detector="CREDIT_CARD_NUMBER")
>>> ssn = Detector(min_confidence=Confidence.POSSIBLE, nightfall_detector="US_SOCIAL_SECURITY_NUMBER")
>>> detection_rule = DetectionRule([cc, ssn])
>>> payload = ["hello world", "my SSN is 678-99-8212", "4242-4242-4242-4242"]
>>> findings, _ = nightfall.scan_text( payload, detection_rules=[detection_rule])The Policy (policy) you define indicates what to scan for in your payload with a logical grouped (ANY or ALL) set of Detection Rules (detectionRules).
Detection Rules can be defined two ways:
inline as code, as shown above
in the Nightall app, which you will then reference by UUID.
Learn more about setting up Nightfall in the Nightfall app to create your own Detectors, Detection Rules, and Policies. See Using Pre-Configured Detection Rules for an example as to how to execute queries using an existing Detection Rules UUID.
In the example above, two of Nightfall's native Detectors are being used: US_SOCIAL_SECURITY_NUMBER and CREDIT_CARD_NUMBER.
You can find a full list of native Detectors in the Detector Glossary.
If you don't want to create your Detectors, Detection Rules, and Policies in the Nightfall app, but would prefer to do it in code, it is possible to define Detectors inline with your own regular expressions or word list as well as extend our native Detectors with exclusion and context rules.
When defining a Detection Rule, you configure the minimum confidence level (minConfidence) and minimum number of times the match must be found (minNumFindings) for the rule to be triggered.
Another feature Nightfall offers is the ability to redact sensitive findings. Detectors may be configured (via redactionConfig) to replace the text that triggered them with a variety of customizable masks, including an encrypted version of the text.
In the payload body, you can see that we are submitting a list of three different strings to scan (payload). The first will trigger the U.S. Social Security Detector. The last will trigger the credit card Detector. The middle example will trigger neither.
The Nightfall API returns a response with an array (findings) with a length that corresponds to the length of the payload array. In this example, only the first and last items in the request payload triggered the Detectors, so the second element of the array is empty.
In the first element of the array, you can see details about which Detection Rule was triggered and the data that was found (finding). The response also provides a confidence level (confidence), as well as the location within the original text where the data was found either in terms of bytes (byteRange) or characters (codepointRange).
{
"findings": [
[
{
"finding": "458-02-6124",
"redactedFinding": "XXX-XXXX-XXXX-9293",
"detector": {
"name": "US Social Security Number",
"uuid": "e30d9a87-f6c7-46b9-a8f4-16547901e069"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 39,
"end": 50
},
"codepointRange": {
"start": 39,
"end": 50
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"My Match Rule"
]
}
],
[],
[
{
"finding": "5310-2768-6832-9293",
"redactedFinding": "XXXX-XXXX-XXXX-9293",
"detector": {
"name": "Credit Card Number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 25,
"end": 44
},
"codepointRange": {
"start": 25,
"end": 44
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"redactedLocation": {
"byteRange": {
"start": 25,
"end": 44
},
"codepointRange": {
"start": 25,
"end": 44
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"My Match Rule"
]
}
]
],
"redactedPayload": [
"",
"",
"My credit card number is XXXX-XXXX-XXXX-9293"
]
}Congratulations! You have successfully completed the Nightfall Quickstart.
You can modify the Detectors or payload in the example request to get more practice with the Nightfall API.
Leaked secrets, such as credentials needed to authenticate and authorize a cloud provider’s API request, expose company software, services, infrastructure, and data to hackers.
Nightfall has developed technology to detect secrets and label findings to speed SecOPs workflows from being clogged and eliminate false positive alerts.
Nightfall uses machine learning models trained on a large (millions of lines of code) diverse dataset (including all programming languages and application types) to ensure best-in-class secret detection accuracy and coverage.
For a growing set of the most popular services, Nightfall will:
label detected secrets by vendor and service type (returned the kind field of the response)
label detected secrets as active risks by validating supported credential types with their associated service endpoints (returned as the status of the service)
Our current solution supports the following vendors covering a diverse set of use cases, including cloud storage/infrastructure, communication, social networks, software development, banking, observability, and payment processing.
This list is not static and will continue to grow as we add support for detecting API keys from additional services. If you want to detect API keys from a service not listed below, please contact us.
AWS
Azure
Confluence
Confluent
Datadog
ElasticSearch
GCP
Google API
GitHub
GitLab
JIRA
JWT
Nightfall
Notion
Okta
Paypal
Plaid
Postmark
Postman
RapidAPI
Salesforce
Sendgrid
Slack
Snyk
Splunk
Square
Stripe
Twilio
Zapier
Below is an example of how an AWS Key would be shown in a finding.
{
"finding": "zImaKNJJ8u/seIbm1UszokVz3SSARukJs6cghEBXD",
"detector": {
"name": "API key",
"uuid": "0e95732f-bc5c-448f-9d15-bd1417177360"
},
"confidence": "VERY_LIKELY",
...
"findingMetadata": {
"apiKeyMetadata": {
"status": "ACTIVE",
"kind": "AWS",
"description": "Access Key ID: AKIA52FSMBPZS1JIDTPX"
}
}
}
The following values are returned for the status field:
ACTIVE
EXPIRED
UNVERIFIED
This value will be based on what information is returned by the corresponding service when attempting the validate the key. If no data is returned fro the service, it will be considered UNVERIFIED.
To use this functionality, you use our existing built-in API_KEY detector to scan a data source such as Git Repository. Below is an example using a detection rule defined in line for a text scan.
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '{
"policy": {
"detectionRules": [
{
"detectors": [
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "API_KEY",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "API Key"
}
],
"name": "My Match Rule",
"logicalOp": "ANY"
}
]
},
"payload": [
"Is this an active nightfall key? NF-OZ6F9fzF2z5mRxMrUdfL8FddFS51kPzE"
]
}'To prevent misuse and ensure the stability of our platform, we enforce a rate limit on an API Key and endpoint basis, similar to the way many other APIs enforce rate limits.
When operating under our Free plan, accounts and their corresponding API Keys have a rate limit of 5 requests per second on average, with support for bursts of 15 requests per second. If you upgrade to a paid plan – the Enterprise plan – this rate increases to a limit of 10 requests per second on average and bursts of 50 requests per second.
Free
5
15
Enterprise
10
50
The Nightfall API follows standard practices and conventions to signal when these rate limits have been exceeded.
Successful requests return a header X-Rate-Limit-Remaining with the integer number of requests remaining before errors will be returned to the client.
When your application exceeds the rate limit for a given API endpoint, the Nightfall API will return an HTTP response code of 429 "Too Many Requests.” If your use case requires increased rate limiting, please reach out to [email protected].
Additionally, these unsuccessful requests return the number of seconds to wait before retrying the request in a Retry-After Header.
Your Request Rate Limiting throttles how frequently you can make requests to the API. You can monitor your rate limit usage via the `X-Rate-Limit-Remaining` header, which tells you how many remaining requests you can make within the next second before being throttled.
Your Quota limits how many requests you can make within a given period. Your current remaining quota and the end of your current quota period are denoted by the following response headers.
X-Quota-Remaining
string
The requests remaining in your quota for this period. Will be reset to the amount specified in your billing plan at the end of your quota cycle.
X-Quota-Period-End
datetime
The date and time at which your quota will be reset, encoded as a string in the RFS-3339 format.
For the free plan, we allow 5 requests per second and 10000 requests in a day.
In two ways:
Nightfall’s out of the box detectors can be modified with context rules and exclusion rules.
Nightfall also supports inputting custom regular expressions or word lists (i.e. dictionaries) as detectors in the RE2 standard as documented here.
Firewall for AI provides a flexible and extensible API that allows you to scan a wide variety of data types, including plain text, structured and unstructured files, and even images. Our API can handle data in various formats such as JSON, XML, CSV, and more. Visit our detector glossary at docs.nightfall.ai/docs/detector-glossary to explore the comprehensive list of supported data types and file formats
Firewall for AI is a powerful API that acts as a middleware layer or client wrapper to protect your AI models from consuming sensitive data. By integrating Firewall for AI into your application via API calls, you can proactively prevent data leaks and maintain compliance without disrupting your existing workflows or model updates.
To prevent misuse and ensure the stability of our platform, we enforce a rate limit on an API Key and endpoint basis, similar to the way many other APIs enforce rate limits.
When operating under our Free plan, accounts and their corresponding API Keys have a rate limit of 5 requests per second on average, with support for bursts of 15 requests per second. If you upgrade to a paid plan – the Enterprise plan – this rate increases to a limit of 10 requests per second on average and bursts of 50 requests per second.
Free
5
15
Enterprise
10,
50,
The Nightfall API follows standard practices and conventions to signal when these rate limits have been exceeded.
Successful requests return a header X-Rate-Limit-Remaining with the integer number of requests remaining before errors will be returned to the client.
When your application exceeds the rate limit for a given API endpoint, the Nightfall API will return an HTTP response code of 429 "Too Many Requests.” If your use case requires increased rate limiting, please reach out to [email protected].
Additionally, these unsuccessful requests return the number of seconds to wait before retrying the request in a Retry-After Header.
Your Request Rate Limiting throttles how frequently you can make requests to the API. You can monitor your rate limit usage via the `X-Rate-Limit-Remaining` header, which tells you how many remaining requests you can make within the next second before being throttled.
Your Quota limits how many bytes of data you're permitted to scan within a given period. Your current remaining quota and the end of your current quota period are denoted by the following response headers.
X-Quota-Remaining
string
The bytes remaining in your quota for this period. Will be reset to the amount specified in your billing plan at the end of your quota cycle.
X-Quota-Period-End
datetime
the date and time at which your quota will be reset, encoded as a string in the RFS-3339 format.
The native SaaS app APIs can be utilized by customers using Nightfall’s SaaS apps, supported natively, to fetch violations, search violations by app meta-data attributes, and fetch findings within violations. These DLP APIs do not provide access to violations for apps scanned via the developer platform. These APIs require you to create an API key as outlined in the Getting Started with the Developer Platform section. However, to use these APIs, you need not create any detectors, detection rules, and policies in the developer platform.
If you are using Nightfall SaaS apps, you can use APIs to fetch violations, search through the violations, and fetch specific findings within the Violations. To scan data in any custom apps or cloud infrastructure services like AWS S3, you must use the APIs in the DLP APIs - Firewall for AI Platform section.
Policies allow customers to create templates for their most common workflows such as sending alerts when detection rules are triggered.
These policies may be through the dashboard or may be defined programmatically.
When defining an a Policy inline, in addition to specifying the Detection Rules (either by referencing the UUID of an existing Detection Rule or), you must define an alertConfig which will determine where findings are sent.
The alertConfig can be either:
an email address
a Slack channel
a webhook url
a url to a SIEM host as well authentication and other headers
Below is a simple example of a payload with a policy that will send alerts to an email address that you would use with our endpoint for .
You will receive the following response:
Note that you may also use a pre-defined policy defined under Developer Platform > Overview > Policies by copying the Policy UUID and sending a request as shown below.
The policy object supersedes the config object. The use of config objects will still continue to be supported, but its use should be considered deprecated. If you specify policy object you cannot also specify a config object.
Also note that previous iterations of the API allowed for a simple list of policyUUIDs to be specified instead of of a policy object. This has been preserved for backwards compatibility, but it is recommended you use the policy object as it has a richer set of features. You may not use both a policyUUIDs list and a policy object.
The following payload will be sent to the given email address with the subject "🚨 Findings Detected by Nightfall! 🚨" as an attachment with the name nightfall-findings.json:
This attachment has the same content as the response payload to the initial request.
Note that the sender address will be [email protected]
This email address will not respond to messages sent to it.
Policies also allow you to send findings to a callback designated URL using the url property of the alertConfig object.
This mechanism allows you to programmatically consume findings and the data sent will contain sensitive information as well as additional metadata like the location of the findings in the payload. For this reason the URL must be an HTTPS URL and the service backing it be implemented to properly respond with your and act as a
Below is what Webhook URL should like in your policy's alertConfig in a payload sent to our endpoint used for scanning plain text.
Another option supported by Policies is sending finding data to a designated Slack channel.
This feature requires that you have configured the .
Below is a sample payload for scanning plain text.
Below is an example as to how the violation will appear in Slack.
See the section on Slack in the overview on for more details.
SIEM (pronounced “sim”) is a combination of security information management (SIM) and security event management systems. SIEM technology collects event log data for analysis in order to provide visibility into network activity.
It is possible to send findings from a policy to a SIEM service such as LogRhythm, SumoLogic, or Splunk using the siem alertConfig.
This configuration will require a URL to a collector that uses an HTTPS endpoint.
Note that the URL for the siem alertConfig must:
use the HTTPS scheme
be able to accept requests made with the POST verb
respond with a 200 status code upon receipt of the event
See the documentation for your SIEM service for how to set up this URL.
Unlike the url alertConfig option, the siem alertConfig does not require that the endpoint for the service implement a custom challenge response. Events sent to the siem alertConfig endpoint contain a subset of what is sent to the url alertConfig. Furthermore the findings are sent in a redacted form similar to Slack or email alerts.
In addition to the URL, you may provide headers such as those that are used for authorization.
The headers in the SIEM alertConfig are divided into sensitiveHeaders and plainTextHeaders header mappings.
The sensitiveHeaders field is specifically for header values like authentication. Nightfall ensures that these header values are always hidden in our service. They are never logged or saved in analytic events.
You can use plainTextHeaders for all other type of information you would like passed along with Nightfall alerts to you HTTP endpoint. Nightfall assumes that the values stored plainTextHeaders do not contain any sensitive information so we do not take any action to hide or protect these values.
Below is an example of a payload using a siem alertConfig.
A policy may be configured with default redaction rules as a defaultRedactionConfig that will affect the content of the redactedPayload field of the content that is sent to the alert locations specified in the policy alertConfig. Note that this redaction does not affect the findings themselves.
These redaction rules will be applied to Detection Rules that do not have a specified redaction configuration.
The redactionConfig specified must be one and only one of the four available redaction types:
maskConfig
infoTypeSubstitutionConfig
substitutionConfig
cryptoConfig
For more information on Redactions see:
Below is a simple example of a payload for using a policy set up to use a defaultRedactionConfig
In additional to a defaultRedactionConfig it is possible to set the number of bytes to include as before and after a given finding as the contextBytes. This context can provide meaning to how the finding appears within the text to allow human readers to better understand the meaning of the finding. The maximum value for contextBytes is 40.
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number"
}
],
"name": "SSN Match Detection Rule",
"logicalOp": "ALL"
}
],
"contextBytes": 5,
"alertConfig": {
"email": {
"address": "[email protected]"
}
}
},
"payload": [
"The customer's social security number is 555-55-5555",
"No SSN in this string"
]
}{
"findings": [
[
{
"finding": "555-55-5555",
"beforeContext": "r is ",
"detector": {
"name": "US Social Security Number",
"uuid": "e30d9a87-f6c7-46b9-a8f4-16547901e069"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 41,
"end": 52
},
"codepointRange": {
"start": 41,
"end": 52
},
"rowRange": null,
"columnRange": null,
"commitHash": ""
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"SSN Match Detection Rule"
]
}
],
[]
],
"redactedPayload": [
"",
""
]
}curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'accept: application/json' \
--header 'authorization: Bearer <InsertYourApiKeyHere>' \
--header 'content-type: application/json' \
--data '
{
"policyUUIDs": [
"2b2ced32-80c3-4a89-8757-489743ec4640"
],
"payload": [
"My payload to scan"
]
}
'{
"redactedPayload": [
"",
""
],
"findings": [
[
{
"confidence": "LIKELY",
"matchedDetectionRules": [
"SSN Match Detection Rule"
],
"matchedDetectionRuleUUIDs": [],
"location": {
"codepointRange": {
"start": 41,
"end": 52
},
"rowRange": null,
"byteRange": {
"start": 41,
"end": 52
},
"columnRange": null,
"commitHash": ""
},
"finding": "555-55-5555",
"detector": {
"name": "SSN Match Detector",
"uuid": "7270ccd5-07c5-44e5-b280-c768e0028963"
},
"beforeContext": "r is "
}
],
[]
]
}
{
"policy": {
"detectionRuleUUIDs": [
"c8d43147-0a63-4c01-8a57-83d8108422f5"
],
"alertConfig": {
"url": {
"address": "https://mywebhookurl.com"
}
}
},
"payload": [
"The customer's social security number is 555-55-5555"
]
}{
"policy": {
"detectionRules": [
{
"detectors": [
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number"
}
],
"name": "Simple SSN Match Detection Rule",
"logicalOp": "ALL"
}
],
"alertConfig": {
"slack": {
"target": "#securityalert"
}
}
},
"payload": [
"The customer's social security number is 555-55-5555",
"No SSN in this string"
]
}{
"policy": {
"detectionRules": [
{
"detectors": [
{
"nightfallDetector": "CREDIT_CARD_NUMBER",
"detectorType": "NIGHTFALL_DETECTOR",
"minConfidence": "POSSIBLE",
"minNumFindings": 1
}
],
"logicalOp": "ALL"
}
],
"alertConfig": {
"email": {
"address": "<your email>"
},
"siem": {
"sensitiveHeaders": {
"Authorization": "Splunk <your token value>"
},
"address": "https://http-inputs-<yourhost>.splunkcloud.com:8088/services/collector/event"
}
}
},
"payload": [
"4916-6734-7572-5015 is my credit card number",
"This string does not have any sensitive data",
"my api key is yr+ZWwIZp6ifFgaHV8410b2BxbRt5QiAj1EZx1qj and my 💳 credit card number 💰 is 30204861594838"
]
}
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number"
}
],
"name": "Simple SSN Match Detection Rule",
"logicalOp": "ALL"
}
],
"defaultRedactionConfig": {
"maskConfig": {
"charsToIgnore": [
"-"
],
"maskingChar": "#",
"numCharsToLeaveUnmasked": 4,
"maskLeftToRight": true
}
},
"contextBytes": 5,
"alertConfig": {
"email": {
"address": "[email protected]"
}
}
},
"payload": [
"The customers social security number is 555-55-5555",
"No SSN in this string"
]
}
In this example, we'll walk through making a request to the scan endpoint.
The endpoint inspects the data you provide via the request body and reports any detected occurrences of the sensitive data types you are searching for.
Please refer to the API reference of the scan endpoint for more detailed information on the request and response schemas.
In this sample request, we provide two main fields:
a policy and its detection rules that we want to use when scanning the text payload
a list of text strings to scan
In the example below we will use a Detection Rule that has been configured in the user interface by supplying its UUID.
The aggregate length of all strings in payload list must not exceed 500 KB, and the number of items in the payload may not exceed 50,000.
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
]
},
"payload": [
"4916-6734-7572-5015 is my credit card number",
"This string does not have any sensitive data",
"my api key is yr+ZWwIZp6ifFgaHV8410b2BxbRt5QiAj1EZx1qj and my 💳 credit card number 💰 is 30204861594838"
]
}
'Alternatively you may define your policy in code by using a built in Nightfall detector from the Detector Glossary as follows:
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'content-type: application/json' \
--data '
{
"policy": {
"detectionRules": [
{
"detectors": [
{
"nightfallDetector": "CREDIT_CARD_NUMBER",
"detectorType": "NIGHTFALL_DETECTOR",
"minConfidence": "POSSIBLE",
"minNumFindings": 1
}
],
"logicalOp": "ALL"
}
]
},
"payload": [
"4916-6734-7572-5015 is my credit card number",
"This string does not have any sensitive data",
"my api key is yr+ZWwIZp6ifFgaHV8410b2BxbRt5QiAj1EZx1qj and my 💳 credit card number 💰 is 30204861594838"
]
}
'See Creating an Inline Detection Rule for more information about how policies and detection rules may be defined through code.
Executing the curl request will yield a response as follows.
{
"findings": [
[
{
"finding": "4916-6734-7572-5015",
"detector": {
"name": "Credit card number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 0,
"end": 19
},
"codepointRange": {
"start": 0,
"end": 19
}
},
"matchedDetectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules": []
}
],
[],
[
{
"finding": "30204861594838",
"detector": {
"name": "Phone number",
"uuid": "d08edfc4-b5e2-420a-a5fe-3693fb6276c4"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 94,
"end": 108
},
"codepointRange": {
"start": 88,
"end": 102
}
},
"matchedDetectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules": []
},
{
"finding": "30204861594838",
"detector": {
"name": "Credit card number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 94,
"end": 108
},
"codepointRange": {
"start": 88,
"end": 102
}
},
"matchedDetectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules": []
}
]
]
}
"location": {
"byteRange": {
"start": 94,
"end": 108
},
"codepointRange": {
"start": 88,
"end": 102
}
},
"matchedDetectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules": []
},
{
"finding": "30204861594838",
"detector": {
"name": "Credit card number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 94,
"end": 108
},
"codepointRange": {
"start": 88,
"end": 102
}
},
"matchedDetectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules": []
}
]
]The API call returns a list, where the item at each index is a sublist of matches for the provided detector types.
The indices of the response list correspond directly to the indices of the list provided in the request payload.
In this example, the first item in the response list contains a finding because one credit card number was detected in the first string we provided. The second item in the response list is an empty list because there is no sensitive data in the second input string we provided. The third item in the returned list contains multiple findings as a result of multiple Detectors within the Detection Rule being triggered.
You can read further about the fields in the response object in the Nightfall APIs.
Nightfall's upload process is built to accommodate files of any size. Once files are uploaded, they may be scanned with Detection Rules and Policies to detect potential violations.
Many users will find it more convenient to use our our native language SDKs to complete the upload process.
Uploading files using Client SDK libraries requires fewer steps as all the required API operations are wrapped in a single function call. Furthermore these SDKs handle all the programmatic logic necessary to send files in smaller chunks to Nightfall.
For users that are looking to understand the entire upload process end-to-end, that is also outlined in this document. We will walk you through the order of operations necessary to upload the file.
Rather than implementing the full sequence of API calls for the upload functionality yourself, the Nightfall’s native language SDKs provide a single method that wraps the steps required to upload your file.
Below is an example of uploading a file from our Python SDK and our Node SDK.
>>> from nightfall import Confidence, DetectionRule, Detector, Nightfall, EmailAlert, AlertConfig
>>> import os
>>> # use your API Key here
>>> nightfall = Nightfall("NF-y0uRaPiK3yG03sH3r3")
>>> # A rule contains a set of detectors to scan with
>>> cc = Detector(min_confidence=Confidence.LIKELY, nightfall_detector="CREDIT_CARD_NUMBER")
>>> ssn = Detector(min_confidence=Confidence.POSSIBLE, nightfall_detector="US_SOCIAL_SECURITY_NUMBER")
>>> detection_rule = DetectionRule([cc, ssn])
>>> # The scanning is done asynchronously, so provide a valid email address as the simplest way of getting results
>>> alertconfig = alert_config=AlertConfig(email=EmailAlert("[email protected]"))
>>> # Upload the file and start the scan.
>>> id, message = nightfall.scan_file( "./README.md", detection_rules=[detection_rule], alert_config=alertconfig)
>>> print("started scan", id, message)//this script assumes the node sdk has been installed locally with `npm install` and `npm run build`
import { Nightfall } from "./nightfall-nodejs-sdk/dist/nightfall.js";
import { Detector } from "./nightfall-nodejs-sdk/dist/types/detectors.js";
// By default, the client reads your API key from the environment variable NIGHTFALL_API_KEY
const uploadit = async() => {
var data = null;
const nfClient = new Nightfall();
try{
const response = await nfClient.scanFile('./README.md', {
detectionRules: [
{
name: 'Secrets Scanner',
logicalOp: 'ANY',
detectors: [
{
minNumFindings: 1,
minConfidence: Detector.Confidence.Possible,
displayName: 'Credit Card Number',
detectorType: Detector.Type.Nightfall,
nightfallDetector: 'CREDIT_CARD_NUMBER',
},
],
},
],
alertConfig: {
email: {
address: "[email protected]"
}
}
});
if (response.isError) {
data = response.getError();
}
else{
data = (response.data.id);
}
}
catch(e){
console.log(e);
}
return data;
}
uploadit().then(data => console.log(data));To run the node sample script you must compile it as TypesScript. Save it as a .ts file and run
tsc <yourfilename>.ts -lib ES2015,DOM
You can then run the resulting JavaScript file:
NIGHTFALL_API_KEY=<YourApiKey> node yourscriptname.js
Note that these examples use an email address to receive the results for simplicity.
You may also want to use a webhook. See Webhooks and Asynchronous Notifications for additional information on how to set up Webhook server to receive these results.
The upload process consists of 3 stages:
Once the upload is complete, you may initiate the file scan.
After we discuss each API call in the sequence, you will find a script that walks through the full sequence at the end of this guide.
POST /v3/upload
The first step in the process of scanning a binary file is to initiate an upload in order to get a fileId through the Initiate a File Upload endpoint.
As part of the initialization you must provide the total byte size of the file being uploaded.
You may also provide the mime-type, otherwise the system will attempt to determine it once the upload is complete.
curl --location --request POST 'https://api.nightfall.ai/v3/upload' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--data-raw '{
"fileSizeBytes": 73891,
"mimeType" : "image/png"
}'The id of the returned JSON object will be used as the fileId in subsequent requests.
The chunkSize is the maximum number of bytes to upload during the uploading phase.
{
"id": "f9dbdb15-c9fa-46ff-86ec-cd5c09aa550d",
"fileSizeBytes": 73891,
"chunkSize": 10485760,
"mimeType": "image/png"
}PATCH /v3/upload/<uploadUUID>Use the Upload a Chunk of a File endpoint to upload the file contents in chunks.
The size of these chunks are determined by the chunkSize value returned by POST /upload endpoint used in the previous step.
Below is a simple example where the file is less than the chunkSize so may safely be uploaded with one call to the upload endpoint.
curl --location --request PATCH 'https://api.nightfall.ai/v3/upload/f9dbdb15-c9fa-46ff-86ec-cd5c09aa550d' \
--header 'X-Upload-Offset: 0' \
--header 'Content-Type: application/octet-stream' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--data-binary '@/Users/myname/Documents/work/Nightfall/Nightfall Upload Sequence.png'If your file's size exceeds the chunkSize, to upload the complete file you will need to send iterative requests as you read portions of the file's contents. This means you will send multiple requests to the upload endpoint as shown above. As you do so, you will be updating the value of the X-Upload-Offset header based on the portion of the file being sent.
Each request should send a chunk of the file exactly chunkSize bytes long except for the final uploaded chunk. The final uploaded chunk is allowed to contain fewer bytes as the remainder of the file may be less than the chunkSize returned by the initialization step.
The request body should be the contents of the chunk being uploaded.
The value of the X-UPLOAD-OFFSET header should be the byte offset specifying where to insert the data into the file as an integer. This byte offset is zero-indexed.
Successful calls to this endpoint return an empty response with an HTTP status code of 204
See the full example script below for an illustration as to how this upload process can be done programmatically.
POST /v3/upload/<uploadUUID>/finish
Once all chunks are uploaded, mark the upload as completed using the Complete a File Upload endpoint.
curl --location --request POST 'https://api.nightfall.ai/v3/upload/f9dbdb15-c9fa-46ff-86ec-cd5c09aa550d/finish' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--data-raw '""'When an upload completes successfully, the returned payload will indicate the mimeType the system determined to file to be if it was not provided during upload initialization.
{
"id": "152848af-2ac9-4e0a-8563-2b82343d964a",
"fileSizeBytes": 2349,
"chunkSize": 10485760,
"mimeType": "application/zip"
}Once a file has been marked as completed, you may initiate a scan of the uploaded file.
After an upload is finalized, it can be scanned against a Detection Policy. A Detection Policy represents a pairing of:
a webhook URL
a set of detection rules to scan data against
The scanning process is asynchronous, with results being delivered to the webhook URL configured on the detection policy. See Webhooks and Asynchronous Notifications for more information about creating a Webhook server.
Exactly one policy should be provided in the request body, which includes a webhookURL to which the callback will be made once the file scan has been completed (this must be an HTTPS URL) as well as a Detection Rule as either an a list of UUIDs or as a rule that has been defined in-line.
You may also supply a value to the requestMetadata field to help identify the input file upon receiving a response to your webhook. This field has a maximum length 10 KB.
curl --request POST \
--url https://api.nightfall.ai/v3/upload/f9dbdb15-c9fa-46ff-86ec-cd5c09aa550d/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"webhookURL": "https://mycompany.org/webhookservice"
},
"requestMetadata": "your file metadata"
}
'Nightfall will verify that the webhook URL is valid before launching its asynchronous scan by issuing a challenge.
Below is a sample Python script that handles the complete sequence of API calls to upload a file using a path specified as an argument.
from os import getenv, path
import fire
import requests
BASE_UPLOAD_URL = getenv("FILE_UPLOAD_HOST", "http://api.nightfall.ai/v3")
NF_API_KEY = getenv("NF_API_KEY")
def upload(filepath, mimetype, policy_uuid):
"""Upload the given file using the provided MIMEType and PolicyUUID.
Arguments:
file_path -- an absolute or relative path to the file that will be
uploaded to the API.
mimetype -- (optional) The mimetype of the file being uploaded.
policy_uuid -- The UUID corresponding to an existing policy. This
policy must be active and have a webhook URL associated with it.
"""
default_headers = {
"Authorization": F"Bearer {NF_API_KEY}",
}
# =*=*=*=*=* Initiate Upload =*=*=*=*=*=*
file_size = path.getsize(filepath)
upload_request_body = {"fileSizeBytes": file_size, "mimeType": mimetype}
r = requests.post(F"{BASE_UPLOAD_URL}/upload",
headers=default_headers,
json=upload_request_body)
upload = r.json()
if not r.ok:
raise Exception(F"Unexpected error initializing upload - {upload}")
# =*=*=*=*=*=* Upload Chunks =*=*=*=*=*=*
chunk_size = upload["chunkSize"]
i = 0
with open(filepath, "rb") as file:
while file.tell() < file_size:
upload_chunk_headers = {
**default_headers,
"X-UPLOAD-OFFSET": str(file.tell())
}
r = requests.patch(F"{BASE_UPLOAD_URL}/upload/{upload['id']}",
headers=upload_chunk_headers,
data=file.read(chunk_size))
if not r.ok:
raise Exception(F"Unexpected error uploading chunk - {r.text}")
i += 1
# =*=*=*=*=*=* Finish Upload =*=*=*=*=*=*
r = requests.post(F"{BASE_UPLOAD_URL}/upload/{upload['id']}/finish",
headers=default_headers)
if not r.ok:
raise Exception(F"Unexpected error finalizing upload - {r.text}")
# =*=*=*=*=* Scan Uploaded File =*=*=*=*=*
r = requests.post(F"{BASE_UPLOAD_URL}/upload/{upload['id']}/scan",
json={"policyUUID": policy_uuid},
headers=default_headers)
if not r.ok:
raise Exception(F"Unexpected error initiating scan - {r.text}")
print("Scan Initiated Successfully - await response on configured webhook")
quota_remaining = r.headers.get('X-Quota-Remaining')
if quota_remaining is not None and int(quota_remaining) <= 0:
print(F"Scan quota exhausted - Quota will reset on {r.headers['X-Quota-Period-End']}")
if __name__ == "__main__":
fire.Fire(upload)The Firewall for AI Platform differs from other solutions like Google DLP and Amazon Macie, as well as open source solutions like truffleHog, on a number of dimensions summarised below.
Accuracy
While solutions like Google DLP have a broad set of detectors, many of them are rules or regex based, which means many of the detectors are not usable in practice. Likewise, detection has been found to be inconsistent in some cases, perhaps due to internal A/B testing.
Because of the limitations of regex-based rules, instead of leveraging machine learning based detectors, OSS detection solutions tend to have a much higher rate of false positives compared to Nightfall.
Detector configurability and ability to provide metrics at the token level makes Nightfall accurate and actionable to engineering & security teams.
Convenience
Want to leave the last 4 digits of a credit card number visible, securely encrypt emails, and completely remove SSNs from your data? The Nightfall platform allows you to redact/replace, substitute, and/or encrypt sensitive data findings in the same API call as your inspection request.
Ease of use
All inspection configuration in Google DLP is done as code, which makes it challenging to easily update, visualize, and modify detection rules and configuration. Nightfall allows for configuration as code, as well as the Nightfall Dashboard for creating and updating detection rules, which makes it easier to collaborate.
OSS secret detection tools tend to rely heavily on manual creation of regex-based detection compared to an ability to programmatically scan text and file inputs using 150+ detectors in Nightfall – e.g. truffleHog only enables you to scan for secrets like passwords and private keys whereas Nightfall scans for not only secrets and credentials, but also allows you to use our vast detector library to scan for PII, PCI, and PHI.
File parsing
To parse files with Google DLP and Macie, each requires that they be in their respective cloud storage (Google Cloud Storage or S3, respectively). With the Nightfall Developer Platform, we take care of storage requirements for you. Uploaded assets are stored encrypted at rest with minimal access permissions, and are automatically deleted after 24 hours.
Amazon’s file parsers are limited to around 20 file types. Most notably, Macie does not support images. Text extraction via machine-learning based OCR for images is a core component of Nightfall’s file scanning endpoint.
Open source secrets detection solutions are limited in their detection capabilities. Namely, these projects do not support scanning binary files. Nightfall supports binary files and the ability to scan diff files.
Platform agnostic
Each cloud provider's DLP products are geared towards protecting their own cloud services. For example, Google DLP’s native integrations are limited to Google Cloud offerings such as BigQuery. Similarly, Macie is primarily designed around scanning AWS S3 buckets. The interface is largely geared towards exploring sensitive data across S3 buckets. To scan content outside of S3, Amazon’s recommendation is to move or replicate the data into S3 to scan, which is impractical.
OSS solutions are primarily designed around git repositories.
Nightfall has native integrations with many cloud applications like Slack, Atlassian, GitHub, Google Drive, as well a broad set of tutorials and open source code so you can build integrations into any data silo with ease. For example, this includes services like Snowflake, Airtable, and more.
Support and documentation
Google DLP and Macie are loosely supported products and with many cloud offerings, support is hard to come by. Nightfall is laser-focused on best-of-breed content inspection and we are ready to address your questions and use cases.
Nightfall also has extensive documentation including SDKs for multiple languages including Python, Java, NodeJS, and Go - with more under consistent development.
Cost and scale
Costs can balloon quickly with commercial services. They also have rate limits that don’t suit high data volumes.
Open source solutions have high hidden costs in the form of TCO, maintenance, and opportunity cost.
Nightfall offers a custom enterprise tier that can help you scale pricing based on your anticipated usage as well as custom rate limits.
Say you have a number of files containing customer or patient data and you are not sure which of them are ok to share in a less secure manner. By leveraging Nightfall’s API you can easily verify whether a file contains sensitive PII, PHI, or PCI.
To make a request to the Nightfall API you will need:
A Nightfall API key
A list of data types you wish to scan for
Data to scan. Note that the API interprets data as plaintext, so you may pass it in any structured or unstructured format.
You can read more about or about our in the linked reference guides.
To run the following API call, we will be using Python's standard json, os, and requests libraries.
First we define the endpoint we want to reach with our API call.
Next we define the headers of our API request. In this example, we have our API key set via an environment variable called "NIGHTFALL_API_KEY". Your API key should never be hard-coded directly into your script.
Next we define the detectors with which we wish to scan our data. The detectors must be formatted as a list of key-value pairs of format {‘name’:’DETECTOR_NAME’}.
Next, we build the request body, which contains the detectors from above, as well as the raw data that you wish to scan. In this example, we will read it from a file called sample_data.csv.
Here we assume that the file is under the 500 KB payload limit of the Scan API. If your file is larger than the limit, consider breaking it down into smaller pieces across multiple API requests.
Now we are ready to call the Nightfall API to check if there is any sensitive data in our file. If there are no sensitive findings in our file, the response will be "[[]]".
[[]]
import json
import os
import requestsendpoint = 'https://api.nightfall.ai/v1/scan'h = {
'Content-Type': 'application/json',
'x-api-key': os.getenv('NIGHTFALL_API_KEY')
}with open('sample_data.csv', 'r') as f:
raw_data = f.read()
d = {
'detectors': detector_object,
'payload':{'items':[raw_data]}
}import os
if os.stat('sample_data.csv').st_size < 500000:
print('This file will fit in a single API call.')
else:
print('This file will need to be broken into pieces across multiple calls.')response = requests.post(endpoint, headers = h, data = json.dumps(d))
if (response.status_code == 200) & (len(response.content.decode()) > 4):
print('This file contains sensitive data.')
print(json.loads(response.content.decode()))
elif response.status_code == 200:
print('No sensitive data detected. Hooray!')
else:
print(f'Something went wrong -- Response {response.status_code}.')[
[
{'fragment': '172-32-1176',
'detector': 'US_SOCIAL_SECURITY_NUMBER',
'confidence': {'bucket': 'LIKELY'},
'location': {'byteRange': {'start': 122, 'end': 133},
'unicodeRange': {'start': 122, 'end': 133}}},
{'fragment': '514-14-8905',
'detector': 'US_SOCIAL_SECURITY_NUMBER',
'confidence': {'bucket': 'LIKELY'},
'location': {'byteRange': {'start': 269, 'end': 280},
'unicodeRange': {'start': 269, 'end': 280}}},
{'fragment': '213-46-8915',
'detector': 'US_SOCIAL_SECURITY_NUMBER',
'confidence': {'bucket': 'LIKELY'},
'location': {'byteRange': {'start': 418, 'end': 429},
'unicodeRange': {'start': 418, 'end': 429}}}
]
]detector_list = ['US_SOCIAL_SECURITY_NUMBER', 'ICD9_CODE', 'US_DRIVERS_LICENSE_NUMBER']
detector_object = [{'name':detector} for detector in detector_list][{'name':'US_SOCIAL_SECURITY_NUMBER'},
{'name':'ICD9_CODE'},
{'name':'US_DRIVERS_LICENSE_NUMBER'}]This guide describes how to use Nightfall with the Python programming language.
The example below will demonstrate how to use Nightfall’s text scanning functionality to verify whether a string contains sensitive PII using the Nightfall Python SDK.
To request the Nightfall API you will need:
A Nightfall API key
An existing Nightfall Detection Rule
Data to scan. Note that the API interprets data as plaintext, so you may pass it in any structured or unstructured format.
You can read more about obtaining a Nightfall API key or about our available data detectors in the linked reference guides.
In this tutorial, we will be downloading, setting up, and using the Python SDK provided by Nightfall.
We recommend you first set up a virtual environment. You can learn more about that here.
You can download the Nightfall SDK from PyPi like this:
We will be using the built-in os library to help run this sample API script. This will be used to help extract the API Key from the OS as an environment variable.
import os
from nightfall import Confidence, DetectionRule, Detector, LogicalOp, NightfallNext, we extract our API Key, and abstract a nightfall class from the SDK, for it. In this example, we have our API key set via an environment variable called NIGHTFALL_API_KEY. Your API key should never be hard-coded directly into your script.
nightfall = Nightfall(os.environ['NIGHTFALL_API_KEY'])Next we define the Detection Rule with which we wish to scan our data. The Detection Rule can be pre-made in the Nightfall web app and referenced by UUID.
detection_rule_uuid = os.environ.get('DETECTION_RULE_UUID')In this example, we will use some example data in the payload List.
🚧Payload LimitPayloads must be under 500 KB when using the Scan API. If your file is larger than the limit, consider using the file api, which is also available via the Python SDK.
We will ignore the second parameter as we do not have redaction configured for this request.
With the Nightfall API, you can redact and mask your findings. You can add a Redaction Config, as part of your Detection Rule. For more information on how to use redaction, and its specific options, please refer to the guide here.
payload = [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 4916-6734-7572-5015"
]
result, _ = nightfall.scan_text(
payload,
detection_rule_uuids=[detection_rule_uuid]
)payload = [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 4916-6734-7572-5015"
]
result, _ = nightfall.scan_text(
payload,
detection_rules=[
DetectionRule(
name="Sample_Detection_Rule",
logical_op=LogicalOp.ANY,
detectors=[
Detector(
min_confidence=Confidence.VERY_LIKELY,
min_num_findings=1,
display_name="Credit Card",
nightfall_detector="CREDIT_CARD_NUMBER",
),
Detector(
min_confidence=Confidence.VERY_LIKELY,
min_num_findings=1,
display_name="Social",
nightfall_detector="US_SOCIAL_SECURITY_NUMBER",
)
]
)
]
)Now we are ready to review the results from the Nightfall SDK to check if there is any sensitive data in our file. Since the results will be in a dataclass, we can use the built-in __repr__ functions to format the results in a user-friendly and readable manner.
All data and sample findings shown below are validated, non-sensitive, examples of sample data.
If there are no sensitive findings in our payload, the response will be as shown in the 'empty response' pane below:
[
[Finding(finding='458-02-6124', redacted_finding=None, before_context=None, after_context=None, detector_name='US social security number (SSN)', detector_uuid='e30d9a87-f6c7-46b9-a8f4-16547901e069', confidence=<Confidence.VERY_LIKELY: 'VERY_LIKELY'>, byte_range=Range(start=39, end=50), codepoint_range=Range(start=39, end=50), matched_detection_rule_uuids=['c67e3dd7-560e-438f-8c72-6ec54979396f'], matched_detection_rules=[])],
[],
[Finding(finding='4916-6734-7572-5015', redacted_finding=None, before_context=None, after_context=None, detector_name='Credit card number', detector_uuid='74c1815e-c0c3-4df5-8b1e-6cf98864a454', confidence=<Confidence.VERY_LIKELY: 'VERY_LIKELY'>, byte_range=Range(start=25, end=44), codepoint_range=Range(start=25, end=44), matched_detection_rule_uuids=['c67e3dd7-560e-438f-8c72-6ec54979396f'], matched_detection_rules=[])]
][
[Finding(finding='458-02-6124', redacted_finding=None, before_context=None, after_context=None, detector_name='Social', detector_uuid='e30d9a87-f6c7-46b9-a8f4-16547901e069', confidence=<Confidence.VERY_LIKELY: 'VERY_LIKELY'>, byte_range=Range(start=39, end=50), codepoint_range=Range(start=39, end=50), matched_detection_rule_uuids=[], matched_detection_rules=['Sample_Detection_Rule'])],
[],
[Finding(finding='4916-6734-7572-5015', redacted_finding=None, before_context=None, after_context=None, detector_name='Credit Card', detector_uuid='74c1815e-c0c3-4df5-8b1e-6cf98864a454', confidence=<Confidence.VERY_LIKELY: 'VERY_LIKELY'>, byte_range=Range(start=25, end=44), codepoint_range=Range(start=25, end=44), matched_detection_rule_uuids=[], matched_detection_rules=['Sample_Detection_Rule'])],
][[], [], []]And that's it 🎉
You are now ready to use the Python SDK for other scenarios.
This guide describes how to use Nightfall with the Java programming language.
The example below will demonstrate how to use Nightfall’s text scanning functionality to verify whether a string contains sensitive PII using the Nightfall Java SDK.
In this tutorial, we will be downloading, setting up, and using the Java SDK provided by Nightfall.
To make a request to the Nightfall API you will need:
A Nightfall API key
Plaintext data to scan.
You can read more about obtaining API key or about our available data detectors from the linked reference guides.
You can add the Nightfall package to your project by adding a dependency to your pom.xml:
<!--pom.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.foo</groupId>
<artifactId>my-artifact</artifactId>
<version>1.0.0</version>
<name>${project.groupId}:${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>ai.nightfall</groupId>
<artifactId>scan-api</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</project>First add the required imports to the top of the file.
These are the objects we will use from the Nightfall SDK, as well as some collection classes for data handling.
//List of imports
import ai.nightfall.scan.NightfallClient;
import ai.nightfall.scan.model.Confidence;
import ai.nightfall.scan.model.DetectionRule;
import ai.nightfall.scan.model.Detector;
import ai.nightfall.scan.model.LogicalOp;
import ai.nightfall.scan.model.NightfallAPIException;
import ai.nightfall.scan.model.ScanTextConfig;
import ai.nightfall.scan.model.ScanTextRequest;
import ai.nightfall.scan.model.ScanTextResponse;
import java.util.Arrays;
import java.util.List;We can then declare some data to scan in a List:
//Sample Payload
List<String> payload = Arrays.asList(
"hello",
"world",
"my data is 4242-4242-4242-4242 but shhhh 🙊 ",
"my ssn is 678-99-8212"
);Create a ScanTextRequest to scan the payload with. First create a new instance of the credit card detector, and set to trigger if there are any findings that are confidence LIKELY or above.
Add a second detector, looking for social security numbers. Set it to be triggered if there is at least a possible finding.
Combine these detectors into a detection rule, which will return findings if either of these detectors are triggered.
Finally, combine the payload and configuration together as a new ScanTextRequest, and return it.
//Build the Scan Request
public static ScanTextRequest buildScanTextRequest() {
// Define some detectors to use to scan your data
Detector creditCard = new Detector("CREDIT_CARD_NUMBER");
creditCard.setMinConfidence(Confidence.LIKELY);
creditCard.setMinNumFindings(1);
Detector ssn = new Detector("US_SOCIAL_SECURITY_NUMBER");
ssn.setMinConfidence(Confidence.POSSIBLE);
ssn.setMinNumFindings(1);
DetectionRule rule = new DetectionRule(Arrays.asList(creditCard, ssn), LogicalOp.ANY);
ScanTextConfig config = ScanTextConfig.fromDetectionRules(Arrays.asList(rule), 20);
return new ScanTextRequest(payload, config);
}Use the ScanTextRequest instance with a NightfallClient to send your request to Nightfall.
The resulting ScanTextResponse may be used to print out the results:
//Run the Scan Request
public class Runner {
public static void main(String[] args) {
try (NightfallClient c = NightfallClient.Builder.defaultClient()) {
try {
ScanTextResponse response = c.scanText(buildScanTextRequest());
System.out.println("response: " + response.getFindings());
} catch (NightfallAPIException e) {
// not a checked exception, just for illustrative purposes
System.out.println("got error: " + e);
}
}
}
}And that's it 🎉
You are now ready to use the Java SDK for other scenarios.
The file scan API has first-class support for text extraction and scanning on all MIME types enumerated below.
Certain file types receive special handling, such as and , that results in more precise information about the location of findings within the source file.
Files with a MIME type not listed below are processed using an unoptimized text extractor. As a result, the quality of the text extraction for unrecognized types may vary.
application/json
application/x-ndjson
application/x-php
text/calendar
text/css
text/csv (treated as and may be )
text/html
text/javascript
text/plain
text/tab-separated-values (treated as )
text/tsv (treated as )
text/x-php
application/pdf
application/vnd.openxmlformats-officedocument.presentationml.presentation
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (treated as )
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/vnd.ms-excel (treated as )
application/bzip2
application/ear
application/gzip
application/jar
application/java-archive
application/tar+gzip
application/vnd.android.package-archive
application/war
application/x-bzip2
application/x-gzip
application/x-rar-compressed
application/x-tar
application/x-webarchive
application/x-zip-compressed
application/x-zip
application/zip
image/apng
image/avif
image/gif
image/jpeg
image/jpg
image/png
image/svg+xml
image/tiff
image/webp
The file scan API explicitly rejects requests with MIME types that are not conducive to extracting or scanning text. Sample rejected MIME types include:
application/photoshop
audio/midi
audio/wav
video/mp4
video/quicktime
File scans of Microsoft Office, Apache parquet, csv, and tab separated files will provide additional properties to locate findings within the document beyond the standard byteRange, codepointRange, and lineRange properties.
Findings will contain a columnRange and a rowRange that will allow you to identify the specific row and column within the tabular data wherein the finding is present.
This functionality is applicable to the following mime types:
text/csv
text/tab-separated-values
text/tsv
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-excel
data files are also accepted.
Below is a sample match of a spreadsheet containing dummy PII where a SSN was detected in the 2nd column and 55th row.
Findings within csv files may be redacted.
To enable redaction in files, set the enableFileRedaction flag of your policy to "true"
The csv file will be redacted based on the configuration of the defaultRedactionConfig of the policy
Below is an example curl request for a csv file that has already been .
When results are sent to the location specified in the alertConfig (in this case an email address) a redactedFile property will be set with a fileURL in addition the findingsURL
This redacted file will be a modified version of the original csv file.
Below is an example of a redacted csv file.
Nightfall provides special handling for archives of Git repositories.
Nightfall will scan the repository history to discover findings in particular checkin, returning the hash for the checkin.
In order to scan the repository, you will need to create a clone, i.e.
git clone https://github.com/nightfallai/nightfall-go-sdk.git
This creates a clone of the Nightfall go SDK.
You will then need to create an archive that can be uploaded using Nightfall's file scanning sequence.
zip -r directory.zip directory
Note that in order to work, the hidden directory .github must be included in the archive.
When you initiate the with this file, you will receive scan results that contain the commitHash property filled in.
Using the Nightfall go SDK archive created above, a simple example would be to scan for URLs (i.e. strings starting with http:// or https://), which will send results such as the following:
Currently, processing is limited to repositories with a total number of commits lower than 5000.
Large repositories result in a large volume of data sent at once. We are working on changes to allow these and other large surges of data to be processed in a more controlled manner, and will increase the limit or remove it altogether once those changes are complete.
If the finding in a GitHub repository is considered to be sensitive, it should be considered compromised and appropriate mitigation steps (i.e. secrets should be rotated).
To retrieve the specific checkout, you will need to clone the repository, i.e.
git clone https://github.com/nightfallai/nightfall-go-sdk.git
You can then checkout the specific commit using the commit hash returned by Nightfall.
Note that you are in a when workin with this sort of check out of a repository.
CSV Files: Only the first 250,000 rows will be scanned.
Spreadsheet Files: Up to 100,000 rows per sheet will be scanned, with a maximum of 1 million rows across all tabs in multi-sheet spreadsheets.
PDF Files: Scanning is limited to the first 100 pages, including a maximum of 50 images within those pages.
Images: Images smaller than 5KB or larger than 50MB will be excluded from scanning.
Archive Files: A maximum of 1,000 files will be extracted and scanned. Files larger than 100MB requiring extraction will not be scanned.
{
"findings":[
{
"path":"Sheet1 (5)",
"detector":{
"id":"e30d9a87-f6c7-46b9-a8f4-16547901e069",
"name":"US social security number (SSN)",
"version":1
},
"finding":"624-84-9182",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":2505,
"end":2516
},
"codepointRange":{
"start":2452,
"end":2463
},
"lineRange":{
"start":55,
"end":55
},
"rowRange":{
"start":55,
"end":55
},
"columnRange":{
"start":2,
"end":2
},
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"matchedDetectionRules":[
]
},
...curl --request POST \
--url https://api.nightfall.ai/v3/upload/02a0c5e1-c950-4e28-a988-f6fffefc4205/scan \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-<Your API Key>' \
--header 'Content-Type: application/json' \
--data '
{
"policy": {
"detectionRuleUUIDs": [
"950833c9-8608-4c66-8a3a-0734eac11157"
],
"alertConfig": {
"email": {
"address": "<your email addres>"
}
},
"defaultRedactionConfig": {
"maskConfig": {
"charsToIgnore": [
"-",
"@"
],
"maskingChar": "*"
}
},
"enableFileRedaction": true
},
"requestMetadata": "csv redaction test"
}
'{
"errors":null,
"findingsPresent":true,
"findingsURL":"https://files.nightfall.ai/asdfc5e1-c950-4e28-a988-f6fffefc4205.json?Expires=1655324479&Signature=zjo1nT-PECHC-fiTvAgdA8aDnceoY~6iGfzOBCcBjscKqOHnIar8hoH4gGufffiulBw5BpfJuvWwBW~lXO~ZNhN139LDwoTsfLJswJiQCB2Hj-Az0Em6go~1j8WBqCS8G0Gk17M-zcPedHGX3z~1pw8nm5sh6Pa-jJwfw9NIEiqmBb3Vdcj3J-~Wzag~ENV4499rnG299ee-ig5Ms1oVlzycb4YxzgTMrTL5Q07ozNenwFZcGDNQre1inLXmV-m8teLX-K3boklenp9KXiNDDV0wi74ADN-QfIR1q1oU7mEI1f3aVC3kju0QRErp2lsfs08EtZKLE3C4N17jDJdYcw__&Key-Pair-Id=K24YOPZ1EKX0YC",
"redactedFile":{
"fileURL":"https://files.nightfall.ai/asdfc5e1-c950-4e28-a988-f6fffefc4205-redacted.csv?Expires=1655324479&Signature=Hx8kRh88maLeStysy3fsLbFVG9VELEtfemtQe2lWUnFjAMd9HqlEksTmirqAWFWV4zPVUB73izlMj5cSer8v2N5ZCcnD3dz~nnwR4P5LewGJ2CQzGnDnXgh70HW5qp04gnUD-pYWp~bGPVspkJKCkl1zH-EoGonvcNVq3SNsVzOlsVIjep7Y7otQKEEyAZ7JmHiVfuBxrvn8pleuC5lEJ3f9miPyoRqH9DyPlNTJTIuijqe9q32Qcui2RsDR6IT-foFX52dy6rRa01ZV0gZMDWJokMlCr8Iu5An~qnhxC49bqTtI82oz9FcBaP-Yea8cq1TiAfGxX7CJ0~JeTLvr6g__&Key-Pair-Id=K24YOPZ1EKX0YC",
"validUntil":"2022-06-15T20:21:19.750990823Z"
},
"requestMetadata":"csv redaction test",
"uploadID":"02a0c5e1-c950-4e28-a988-f6fffefc4205",
"validUntil":"2022-06-15T20:21:19.723045787Z"
}name,email,phone,alphanumeric
Ulric Burton,*****@*************,*-***-***-****,TEL82EBM1GQ
Wade Jones,******************@***********,(********-****,VVF64PJV2EF
Molly Mccullough,*****************@**********,(********-****,OHO41SFZ2BR
Raja Riggs,************@**********,(********-****,UVD51JTE5NZ
Colin Carter,**********************@*********,(********-****,LNI34LLC5WV// Some code{
"findings":[
{
"path":"f607a067..53e59684/nightfall.go",
"detector":{
"id":"6123060e-2d9f-4f35-a7a1-743379ea5616",
"name":"URL"
},
"finding":"https://api.nightfall.ai/\"",
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":142,
"end":168
},
"codepointRange":{
"start":142,
"end":168
},
"lineRange":{
"start":16,
"end":16
},
"rowRange":{
"start":0,
"end":0
},
"columnRange":{
"start":0,
"end":0
},
"commitHash":"53e59684d9778ceb0f0ed6a4b949c464c24d35ce"
},
"beforeContext":"tp\"\n\t\"os\"\n\t\"time\"\n)\n\nconst (\n\tAPIURL = \"",
"afterContext":"\n\n\tDefaultFileUploadConcurrency = 1\n\tDef",
"matchedDetectionRuleUUIDs":[
"cda0367f-aa75-4d6a-904f-0311209b3383"
],
"matchedDetectionRules":[
]
},
...cd nightfall-go-sdk
git checkout 53e59684d9778ceb0f0ed6a4b949c464c24d35ceIn addition to using pre-defined Detection Rules, you may define Detection Rules within the body of your scan method by either supplying:
the identifier of one of Nightfall's native detectors
the UUID of an a Detector defined through the UI
a Regular Expression
a Word List.
Out of the box, Nightfall comes with an extensive library of native detectors.
In the example below two of Nightfall's native Detectors (detectorType = "NIGHTFALL_DETECTOR") are being used:
US_SOCIAL_SECURITY_NUMBER
CREDIT_CARD_NUMBER.
When defining a Detection Rule in line, you configure the minimum confidence level (minConfidence) and minimum number of times the match must be found (minNumFindings) for the rule to be triggered. .
In the payload body, you can see that we are submitting a list of three different strings to scan (payload). The first will trigger the U.S. Social Security Detector. The last will trigger the credit card Detector. The middle example will trigger neither.
For more information on the parameters related to redaction, see Using Redaction.
curl --request POST \
--url https://api.nightfall.ai/v3/scan \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data '{
"policy": {
"detectionRules": [
{
"detectors": [
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "US_SOCIAL_SECURITY_NUMBER",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "US Social Security Number"
},
{
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "CREDIT_CARD_NUMBER",
"minNumFindings": 1,
"minConfidence": "LIKELY",
"displayName": "Credit Card Number",
"redactionConfig": {
"maskConfig": {
"maskingChar": "👀",
"charsToIgnore": ["-"]
}
}
}
],
"name": "My Match Rule",
"logicalOp": "ANY"
}
]
},
"payload": [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 5310-2768-6832-9293"
]
}'Below is the response payload to the previous request.
{
"findings": [
[
{
"finding": "458-02-6124",
"detector": {
"name": "US Social Security Number",
"uuid": "e30d9a87-f6c7-46b9-a8f4-16547901e069"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 39,
"end": 50
},
"codepointRange": {
"start": 39,
"end": 50
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"My Match Rule"
]
}
],
[],
[
{
"finding": "5310-2768-6832-9293",
"redactedFinding": "👀👀👀👀-👀👀👀👀-👀👀👀👀-👀👀👀👀",
"detector": {
"name": "Credit Card Number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 25,
"end": 44
},
"codepointRange": {
"start": 25,
"end": 44
}
},
"redactedLocation": {
"byteRange": {
"start": 25,
"end": 44
},
"codepointRange": {
"start": 25,
"end": 44
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"My Match Rule"
]
}
]
],
"redactedPayload": [
"",
"",
"My credit card number is 👀👀👀👀-👀👀👀👀-👀👀👀👀-👀👀👀👀"
]
}The following example shows a Detection Rule composed of two Detectors defined using regular expressions – one for the format of an International Standard Recording Code (ISRC) and one for the format of an International Standard Musical Work Code (ISWC) – matching either of which will trigger the Detection Rule (by using the logicalOp “Any”).
We will provide a payload of two strings, one of which will match the ISRC and one of which will match the ISWC.
curl --location --request POST 'https://api.nightfall.ai/v3/scan' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: application/json' \
--data-raw '{
"config": {
"detectionRules": [
{
"detectors": [
{
"regex": {
"isCaseSensitive": false,
"pattern": "[A-Z]{2}-?\\w{3}-?\\d{2}-?\\d{5}"
},
"minNumFindings": 1,
"minConfidence": "POSSIBLE",
"detectorType": "REGEX",
"displayName": "ISRC Code Detector"
},
{
"regex": {
"isCaseSensitive": false,
"pattern": "T-[0-9]{3}\\.[0-9]{3}\\.[0-9]{3}-[0-9]"
},
"minNumFindings": 1,
"minConfidence": "POSSIBLE",
"detectorType": "REGEX",
"displayName": "ISWC Code Detector"
}
],
"name": "ISRC and ISWC Code Detection Rule",
"logicalOp": "ANY"
}
]
},
"payload": [
"Non Matching Payload",
"US-S1Z-99-00001 is an example ISRC Code: ",
"The ISWC for Symphony No. 9 is T-905.029.737-5"
]
}
'The returned response demonstrates how findings are returned, with a finding per payload entry and the Detection Rule and Detector that matched the payload, if any.
The byte range that triggered the match is also provided. In the case of the 2nd item in the payload, since the match occurred at the beginning of the string, it has a location where the byteRange start is 0. In the case of the 3rd payload entry the location offset is 31.
{
"findings": [
[],
[
{
"finding": "US-S1Z-99-00001",
"detector": {
"name": "ISRC Code Detector",
"uuid": "d8be87c9-4b44-41fd-b78c-8d638fe56069"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 0,
"end": 15
},
"codepointRange": {
"start": 0,
"end": 15
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"ISRC and ISWC Code Detection Rule"
]
}
],
[
{
"finding": "T-905.029.737-5",
"detector": {
"name": "ISWC Code Detector",
"uuid": "faf4c830-f2ac-4934-bf9c-ff20f5a6f420"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 31,
"end": 46
},
"codepointRange": {
"start": 31,
"end": 46
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"ISRC and ISWC Code Detection Rule"
]
}
]
]
}The following example shows how a word list may be used instead of a regular expression.
curl --location --request POST 'https://api.nightfall.ai/v3/scan' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'x-api-key: NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--data-raw '{
"config": {
"detectionRules": [
{
"detectors": [
{
"wordList": {
"values": [
"cat",
"dog",
"rat"
],
"isCaseSensitive": false
},
"minNumFindings": 1,
"minConfidence": "POSSIBLE",
"displayName": "animals",
"detectorType": "WORD_LIST"
}
],
"name": "WordListExamples",
"logicalOp": "ANY"
}
]
},
"payload": [
"THE CAT SAT ON THE MAT",
"The dog and the rat are on the west bank of the river",
"No one here but use chickens"
]
}'Below is the resulting payload with the findings detected in our different payload strings.
Note that since the isCaseSensitive flag is set to "false" for the detector, so the first string in our payload matches a word from our word list.
Also note that the confidence level for a word list match defaults to "LIKELY" so you should not set a minConfidence level higher than that if you want matches to result.
{
"findings": [
[
{
"finding": "cat",
"detector": {
"name": "animals",
"uuid": "c033e224-034a-417f-9c0d-0c8d13f462bb"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 4,
"end": 7
},
"codepointRange": {
"start": 4,
"end": 7
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"WordListExamples"
]
}
],
[
{
"finding": "dog",
"detector": {
"name": "animals",
"uuid": "c033e224-034a-417f-9c0d-0c8d13f462bb"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 4,
"end": 7
},
"codepointRange": {
"start": 4,
"end": 7
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"WordListExamples"
]
},
{
"finding": "rat",
"detector": {
"name": "animals",
"uuid": "c033e224-034a-417f-9c0d-0c8d13f462bb"
},
"confidence": "LIKELY",
"location": {
"byteRange": {
"start": 16,
"end": 19
},
"codepointRange": {
"start": 16,
"end": 19
}
},
"matchedDetectionRuleUUIDs": [],
"matchedDetectionRules": [
"WordListExamples"
]
}
],
[]
],
"redactedPayload": [
"",
"",
""
]
}This guide describes how to use Nightfall with the Ruby programming language.
The example below will demonstrate how to use Nightfall’s text scanning functionality to verify whether a string contains sensitive PII using the Nightfall Python SDK.
To follow along, you will need:
A Nightfall API Key
An existing Detection Rule
Data to scan. Note that the API interprets data as plaintext, so you may pass it in any structured or unstructured format.
A local Ruby 2.6 or greater environment.
Start by creating a new file called nightfall_demo.rb
Now we will walk through the code step by step. If you'd like to skip ahead you can see the complete code sample at the bottom of this page.
We will be using a few built-in Ruby libraries to run this sample API script.
First, we will load some environment variables that will be used to interact with the Nightfall API. NIGHTFALL_API_KEY should be your Nightfall API Key, and NIGHTFALL_DETECTION_RULE_UUID should be the UUID for your existing Nightfall condition set.
Next, we will construct our payload to scan as an array. You can replace this with any data you'd like, or read plaintext from a file.
Next, we build the HTTP request headers and body using the environment variables that we previously defined.
Next, we build the HTTP object and make a request to the Nightfall API.
Lastly, we make the API request and process the response from Nightfall. If there are sensitive findings in the response we pretty-print them to the console. If there are no findings, we print a message to the console. Otherwise, if there is a problem with the HTTP request we print the status code and message to the console.
Now we can run our script:
If there are sensitive findings based on your Nightfall detection rule, you should see output similar to this in your console, corresponding to each of the 3 items inputted to scan in the payload.
Congrats 🎉. You've successfully scanned text for sensitive data with Ruby using the Nightfall API.
For your convenience, the complete Ruby code sample is shown below.
# Load dependencies
require 'open-uri'
require 'net/http'
require 'json'# Load environment variables for Nightfall API
nightfall_api_key = ENV['NIGHTFALL_API_KEY']
detection_rule_uuid = ENV['NIGHTFALL_DETECTION_RULE_UUID']# Text data to scan
payload = [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 4916-6734-7572-5015"
]# Configure detection settings
config = {
"config": {
"detectionRuleUUIDs": [detection_rule_uuid]
},
"payload": payload
}# Build API request
url = URI("https://api.nightfall.ai/v3/scan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Accept"] = 'application/json'
request["Content-Type"] = 'application/json'
request["Authorization"] = "Bearer #{nightfall_api_key}"
request.body = config.to_json# Make API request
response = http.request(request)
# Parse response
if response.code.to_i == 200 and response.body['findings']
puts "This text contains sensitive data.\n\n"
puts JSON.pretty_generate(JSON.parse(response.body))
elsif response.code.to_i == 200
puts "No sensitive data found. Hooray!"
else
puts "Something went wrong -- Response #{response.code}."
endruby nightfall_demo.rbThis text contains sensitive data.
{
"findings": [
[
{
"finding": "458-02-6124",
"detector": {
"name": "US social security number (SSN)",
"uuid": "e30d9a87-f6c7-46b9-a8f4-16547901e069"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 39,
"end": 50
},
"codepointRange": {
"start": 39,
"end": 50
}
},
"matchedDetectionRuleUUIDs": [
"996a3c12-35d1-48cb-b858-5ee0841c652d"
],
"matchedDetectionRules": [
]
}
],
[
],
[
{
"finding": "4916-6734-7572-5015",
"detector": {
"name": "Credit card number",
"uuid": "74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence": "VERY_LIKELY",
"location": {
"byteRange": {
"start": 25,
"end": 44
},
"codepointRange": {
"start": 25,
"end": 44
}
},
"matchedDetectionRuleUUIDs": [
"996a3c12-35d1-48cb-b858-5ee0841c652d"
],
"matchedDetectionRules": [
]
}
]
],
"redactedPayload": [
"",
"",
""
]
}
# nightfall_demo.rb
# Load dependencies
require 'open-uri'
require 'net/http'
require 'json'
# Load environment variables for Nightfall API
nightfall_api_key = ENV['NIGHTFALL_API_KEY']
detection_rule_uuid = ENV['NIGHTFALL_DETECTION_RULE_UUID']
# Text data to scan
payload = [
"The customer social security number is 458-02-6124",
"No PII in this string",
"My credit card number is 4916-6734-7572-5015"
]
# Configure detection settings
config = {
"config": {
"detectionRuleUUIDs": [detection_rule_uuid]
},
"payload": payload
}
# Build API request
url = URI("https://api.nightfall.ai/v3/scan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Accept"] = 'application/json'
request["Content-Type"] = 'application/json'
request["Authorization"] = "Bearer #{nightfall_api_key}"
request.body = config.to_json
# Make API request
response = http.request(request)
# Parse response
if response.code.to_i == 200 and response.body['findings']
puts "This text contains sensitive data.\n\n"
puts JSON.pretty_generate(JSON.parse(response.body))
elsif response.code.to_i == 200
puts "No sensitive data found. Hooray!"
else
puts "Something went wrong -- Response #{response.code}."
endThe Nightfall API is capable of returning a redacted version of your scanned text when a Detector is triggered.
This functionality allows you to hide potentially sensitive information while retaining the original context in which that information appeared.
In order to redact content, when you call the scan endpoint you must provide a RedactionConfig as part of the definition of your Detection Rule.
You may specify one of the following different methods to redact content:
apply masking (e.g. asterisks)
substitute a custom phrase
substitute the name of the Detector triggered (referred to as "InfoType substitution")
use encryption
A RedactionConfig is defined per Detector in a Detection Rule, allowing you to specify a different redaction method for each type of Detector in the rule.
By default, the redaction feature will return both the sensitive finding and the redacted version of that finding. You may set the removeFinding field to true if you want only the redacted version of the finding returned in the response.
Specifying a MaskConfig as part of your RedactionConfig substitutes a character for each character in the matched text. By default the masking character is an asterisk (*). You may specify an alternate character to use instead (maskingChar).
You may also choose to only mask a portion of the original text by specifying a number of characters to leave unmasked (numCharsToLeaveUnmasked). For instance, if you want to mask all but the last 4 digits of a credit card number, set this value to 4 so that the redacted finding would be rendered as ***************4242.
In the case where you want to leave characters unmasked at the front of the string you may use the maskLeftToRight flag. This flag determines if masking is applied left to right (*****/1984) instead of right to left (01/01*****). By default, this value is false.
Below is an example of how a RedactionConfig would be configured to redact the text that triggers a DATE_OF_BIRTH Detector such that the text 01/11/1995 becomes ??/??/??95
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"DATE_OF_BIRTH",
"redactionConfig":{
"maskConfig":{
"charsToIgnore":[
"/"
],
"maskingChar":"?",
"maskRightToLeft":true,
"numCharsToLeaveUnMasked":2
}
}
}The SubstitutionConfig substitutes a sensitive finding with the value assigned to the property substitutionPhrase.
If no value is assigned to substitutionPhrase, the finding will be replaced with an empty string.
It is possible to replace a sensitive finding with the name of the NIGHTFALL_DETECTOR that triggered it by using an InfoTypeSubstitutionConfig.
If you use the built in credit card Detector, the string 4242-4242-4242-4242 will be redacted to [CREDIT_CARD_NUMBER]
This config is only valid for Detector's with a detectorType of NIGHTFALL_DETECTOR.
A CryptoConfig will encrypt a sensitive finding with a public key (provided as the publicKey property of the config) using RSA encryption.
Note that you are responsible for passing public keys for encryption and handling any decryption of the response payload. Nightfall will not store your keys.
Below is an example of a CryptoConfig being used to redact an EMAIL_ADDRESS detector.
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"EMAIL_ADDRESS",
"displayName":"email",
"redactionConfig":{
"cryptoConfig":{
"publicKey":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAydYMwOYUGyBXDgHkzv19YR/dYQES4kYTMUps39qv/amNDywz4nsBDvCUqUvcN3nEpplHlYGH5ShSeA4G/FcmRqynSLVyFPZat/8E7n+EeHsgihFrr8oDWo5UBjCwRinTrC0m11q/5SeNzwVCWkf9x40u94QBz13dQoa9yPwaZBX5uBzyH86R7yeZHpad2cLq0ltpmJ3j5UfsFilkOb3JB60TNpNDdfabprot/y30CEnDDOgAXGtV1m0AhQpQjKRnkUs39DntqSbS+i0UgbyqzEGNUkeR1WsotXekW4KnbWA7k6S8SfkO27vnTSY5b9g/KKaOdysn5YaWJPfTVT/nywIDAQAB\n-----END PUBLIC KEY-----"
}
}
}The results of applying redactions are returned in the response payload for requests made to the scan endpoint as both part of an array named redactedPayload as well as additional properties of the finding object.
The original input payload with redactions made inline are returned as a list of strings under the redactedPayload property. Each item in the list of redacted payloads corresponds to the list of strings in the original input payload and, if a Detector was triggered, it will contain a redacted version of that corresponding string.
If an item in the input payload did not have any findings, the entry for that index will be an empty string ("").
The redactedPayload property is omitted if no RedactionConfig was provided.
Additionally, the fields redactedFinding and redactedLocation are added to the finding object when the redaction feature is invoked.
The redactedFinding field contains the redacted version of only the text of the finding without its surrounding context. This is useful when you are masking a portion of the text that triggered a Detector.
The redactedLocation property will be returned as part of the finding that corresponds to an item in the payload. This may be distinct from the location property that is returned for a finding by default.
In the unlikely case where there are findings that overlap, Nightfall will default to replacing the text of the overlapping findings with [REDACTED BY NIGHTFALL].
The following example shows how the redaction functionality may be invoked, with a variety of different redaction methods applied to the different Detectors being used.
curl --location --request POST 'https://api.nightfall.ai/v3/scan' \
--header 'x-api-key: NF-rEpLaCeM3w1ThYoUrNiGhTfAlLKeY123' \
--header 'Content-Type: text/plain' \
--data-raw '{
"payload":[
"my ssn is 123-45-5555 and date of birth is 01/11/1995 and my credit card number is 4242 4242 4242 4242 and my email is [email protected].",
"my date of birth is 03 23 4242 4242 4242 4242 amex"
],
"policy":{
"detectionRules":[
{
"detectors":[
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"CREDIT_CARD_NUMBER",
"displayName":"cc",
"redactionConfig":{
"infoTypeSubstitutionConfig":{
},
"removeFinding":true
}
},
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"US_SOCIAL_SECURITY_NUMBER",
"displayName":"ssn",
"redactionConfig":{
"substitutionConfig":{
"substitutionPhrase":"*REDACTED*"
}
}
},
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"EMAIL_ADDRESS",
"displayName":"email",
"redactionConfig":{
"cryptoConfig":{
"publicKey":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAydYMwOYUGyBXDgHkzv19YR/dYQES4kYTMUps39qv/amNDywz4nsBDvCUqUvcN3nEpplHlYGH5ShSeA4G/FcmRqynSLVyFPZat/8E7n+EeHsgihFrr8oDWo5UBjCwRinTrC0m11q/5SeNzwVCWkf9x40u94QBz13dQoa9yPwaZBX5uBzyH86R7yeZHpad2cLq0ltpmJ3j5UfsFilkOb3JB60TNpNDdfabprot/y30CEnDDOgAXGtV1m0AhQpQjKRnkUs39DntqSbS+i0UgbyqzEGNUkeR1WsotXekW4KnbWA7k6S8SfkO27vnTSY5b9g/KKaOdysn5YaWJPfTVT/nywIDAQAB\n-----END PUBLIC KEY-----"
}
}
},
{
"minNumFindings":1,
"minConfidence":"POSSIBLE",
"detectorType":"NIGHTFALL_DETECTOR",
"nightfallDetector":"DATE_OF_BIRTH",
"redactionConfig":{
"maskConfig":{
"charsToIgnore":[
"/"
],
"maskingChar":"?",
"maskRightToLeft":true,
"numCharsToLeaveUnMasked":2
}
}
}
],
"name":"cc",
"logicalOp":"ANY"
}
]
}
}'You can see in the response how the RedactionConfig associated with the various Detectors affects the different findings.
Note that because the 2nd item the payload matches multiple detectors, the redacted text in the redactedPayload property becomes [REDACTED BY NIGHTFALL]
{
"findings":[
[
{
"finding":"[email protected]",
"redactedFinding":"X8QL0mZGHZ+N47nPEccjsLHf2F/5cFqjF16P6wgYJhy8IaxHipHWMBRAufKR4T8FFkvTuTEanu6ZAA+V8NTkNmTLxHarcWPSVClJ8kjXAPltLuR4I2H4eeT+sWEvUP3ik/BF1KcxRpsYWDQO1bNYk+WReXkWlW72Q7rbWuTGFj2uDFCPS+DUraDh9wNBsMPELFOnh1GSQIKCp9U5GMp/kkpo/0idh83RVHXyjZPT4ReKEST2oG2lQ9UuP5LJy/mHX1VYgd8DwlETn8nkhqJ1T0mGs6kHSh22G6N0ic0PjHnj73RiMnQdPwlLw3qyPmFf6RRLKtFuzmFan8ZGtZhcKA==",
"detector":{
"name":"email",
"uuid":"c0235299-0f26-4ad6-ad8c-71f83daf44e9"
},
"confidence":"VERY_LIKELY",
"location":{
"byteRange":{
"start":120,
"end":135
},
"codepointRange":{
"start":120,
"end":135
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":120,
"end":135
},
"codepointRange":{
"start":120,
"end":135
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
},
{
"finding":"01/11/1995",
"redactedFinding":"??/??/??95",
"detector":{
"name":"DATE_OF_BIRTH",
"uuid":"540856cb-99cb-42e7-b8aa-cd4f22f019d7"
},
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":43,
"end":53
},
"codepointRange":{
"start":43,
"end":53
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":43,
"end":53
},
"codepointRange":{
"start":43,
"end":53
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
},
{
"finding":"",
"redactedFinding":"[CREDIT_CARD_NUMBER]",
"detector":{
"name":"cc",
"uuid":"74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence":"VERY_LIKELY",
"location":{
"byteRange":{
"start":84,
"end":103
},
"codepointRange":{
"start":84,
"end":103
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":84,
"end":103
},
"codepointRange":{
"start":84,
"end":103
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
},
{
"finding":"123-45-5555",
"redactedFinding":"*REDACTED*",
"detector":{
"name":"ssn",
"uuid":"e30d9a87-f6c7-46b9-a8f4-16547901e069"
},
"confidence":"VERY_LIKELY",
"location":{
"byteRange":{
"start":10,
"end":21
},
"codepointRange":{
"start":10,
"end":21
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":10,
"end":21
},
"codepointRange":{
"start":10,
"end":21
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
}
],
[
{
"finding":"",
"redactedFinding":"[CREDIT_CARD_NUMBER]",
"detector":{
"name":"cc",
"uuid":"74c1815e-c0c3-4df5-8b1e-6cf98864a454"
},
"confidence":"VERY_LIKELY",
"location":{
"byteRange":{
"start":26,
"end":45
},
"codepointRange":{
"start":26,
"end":45
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":26,
"end":45
},
"codepointRange":{
"start":26,
"end":45
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
},
{
"finding":"03 23 4242",
"redactedFinding":"????????42",
"detector":{
"name":"DATE_OF_BIRTH",
"uuid":"540856cb-99cb-42e7-b8aa-cd4f22f019d7"
},
"confidence":"LIKELY",
"location":{
"byteRange":{
"start":20,
"end":30
},
"codepointRange":{
"start":20,
"end":30
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"redactedLocation":{
"byteRange":{
"start":20,
"end":30
},
"codepointRange":{
"start":20,
"end":30
},
"rowRange":null,
"columnRange":null,
"commitHash":""
},
"matchedDetectionRuleUUIDs":[
],
"matchedDetectionRules":[
"cc"
]
}
]
],
"redactedPayload":[
"my ssn is *REDACTED* and date of birth is ??/??/??95 and my credit card number is [CREDIT_CARD_NUMBER] and my email is X8QL0mZGHZ+N47nPEccjsLHf2F/5cFqjF16P6wgYJhy8IaxHipHWMBRAufKR4T8FFkvTuTEanu6ZAA+V8NTkNmTLxHarcWPSVClJ8kjXAPltLuR4I2H4eeT+sWEvUP3ik/BF1KcxRpsYWDQO1bNYk+WReXkWlW72Q7rbWuTGFj2uDFCPS+DUraDh9wNBsMPELFOnh1GSQIKCp9U5GMp/kkpo/0idh83RVHXyjZPT4ReKEST2oG2lQ9UuP5LJy/mHX1VYgd8DwlETn8nkhqJ1T0mGs6kHSh22G6N0ic0PjHnj73RiMnQdPwlLw3qyPmFf6RRLKtFuzmFan8ZGtZhcKA==.",
"my date of birth is [REDACTED BY NIGHTFALL] amex"
]
}Returns a list of repositories Nightfall has access to.
The maximum number of records to be returned in the response
100Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /apps/v1/github/repositories HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"repositories": [
{
"repositoryID": 1,
"repositoryName": "text",
"isRepoPrivate": true,
"repoLink": "text",
"scannedAt": 1,
"isMonitored": true,
"githubUsername": "text"
}
],
"nextPageToken": "text"
}Returns a list of endpoint devices where the Nightfall agent is installed across the organization.
The maximum number of agents to be returned in the response
500Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /apps/v1/endpoint/devices HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"agents": [
{
"os": "MAC_OS",
"deviceName": "text",
"deviceId": "text",
"macAddresses": [
"text"
],
"connectionStatus": "CONNECTED",
"lastConnection": "2025-12-07T05:08:18.301Z",
"agentVersion": "text",
"osVersion": "text"
}
],
"totalAgents": 1,
"nextPageToken": "text"
}The service ingests a local file, scans it for sensitive data with Nightfall, and displays the results in a simple table UI.
We'll deploy the server on Render (a PaaS Heroku alternative) so that you can serve your application publicly in production instead of running it off your local machine. You'll build familiarity with the following tools and frameworks: Python, Flask, Nightfall, Ngrok, Jinja, Render.
Before we get started on our implementation, start by familiarizing yourself with how scanning files works with Nightfall, so you're acquainted with the flow we are implementing.
In a nutshell, file scanning is done asynchronously by Nightfall; after you upload a file to Nightfall and trigger the scan, we perform the scan in the background. When the scan completes, Nightfall delivers the results to you by making a request to your webhook server. This asynchronous behavior allows Nightfall to scan files of varying sizes and complexities without requiring you to hold open a long synchronous request, or continuously poll for updates. The impact of this pattern is that you need a webhook endpoint that can receive inbound notifications from Nightfall when scans are completed - that's what we are building in this tutorial.
You can fork the sample repo and view the complete code here, or follow along below. If you're starting from scratch, create a new GitHub repository.
First, let's start by installing our dependencies. We'll be using Nightfall for data classification, the Flask web framework in Python, and Gunicorn as our web server. Create requirements.txt and add the following to the file:
nightfall
Flask
GunicornThen run pip install -r requirements.txt to do the installation.
Next, we'll need our Nightfall API Key and Webhook Signing Secret; the former authenticates us to the Nightfall API, while the latter authenticates that incoming webhooks are originating from Nightfall. You can retrieve your API Key and Webhook Signing Secret from the Nightfall Dashboard. Complete the Nightfall Quickstart for a more detailed walk-through. Sign up for a free Nightfall account if you don't have one.
These values are unique to your account and should be kept safe. This means that we will store them as environment variables and should not store them directly in code or commit them into version control. If these values are ever leaked, be sure to visit the Nightfall Dashboard to re-generate new values for these secrets.
export NIGHTFALL_API_KEY=<your_key_here>
export NIGHTFALL_SIGNING_SECRET=<your_secret_here>Let's start writing our Flask server. Create a file called app.py. We'll start by importing our dependencies and initializing the Flask and Nightfall clients:
import os
from flask import Flask, request, render_template
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
from datetime import datetime, timedelta
import urllib.request, urllib.parse, json
app = Flask(__name__)
nightfall = Nightfall(
key=os.getenv('NIGHTFALL_API_KEY'),
signing_secret=os.getenv('NIGHTFALL_SIGNING_SECRET')
)Next, we'll add our first route, which will display "Hello World" when the client navigates to /ping simply as a way to validate things are working:
@app.route("/ping")
def ping():
return "Hello World", 200Run gunicorn app:app on the command line to fire up your server, and navigate to your local server in your web browser. You'll see where the web browser is hosted in the Gunicorn logs, typically it will be 127.0.0.1:8000 aka localhost:8000.
[2021-11-26 14:22:53 -0800] [61196] [INFO] Starting gunicorn 20.1.0
[2021-11-26 14:22:53 -0800] [61196] [INFO] Listening at: http://127.0.0.1:8000 (61196)
[2021-11-26 14:22:53 -0800] [61196] [INFO] Using worker: sync
[2021-11-26 14:22:53 -0800] [61246] [INFO] Booting worker with pid: 61246To expose our local webhook server via a public tunnel that Nightfall can send requests to, we'll use ngrok. Download and install ngrok via their quickstart documentation here. We'll create an ngrok tunnel as follows:
After running this command, ngrok will create a tunnel on the public internet that redirects traffic from their site to your local machine. Copy the HTTPS tunnel endpoint that ngrok has created: we can use this as the webhook URL when we trigger a file scan.
Account Nightfall Example
Version 2.3.40
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://3ecedafba368.ngrok.io -> http://localhost:8000
Forwarding https://3ecedafba368.ngrok.io -> http://localhost:8000Let's set this HTTPS endpoint as a local environment variable so we can reference it later:
export NIGHTFALL_SERVER_URL=https://3ecedafba368.ngrok.ioTip: With a Pro ngrok account, you can create a subdomain so that your tunnel URL is consistent, instead of randomly generated each time you start the tunnel.
Before you send a file scan request to Nightfall, let's add logic for our incoming webhook endpoint, so that when Nightfall finishes scanning a file, it can successfully send the sensitive findings to us.
First, what does it mean to have findings? If a file has findings, this means that Nightfall identified sensitive data in the file that matched the detection rules you configured. For example, if you told Nightfall to look for credit card numbers, any substring from the request payload that matched our credit card detector would constitute sensitive findings.
We'll host our incoming webhook at /ingest with a POST method.
Nightfall will POST to the webhook endpoint, and in the inbound payload, Nightfall will indicate if there are sensitive findings in the file, and provide a link where we can access the sensitive findings as JSON.
# respond to POST requests at /ingest
# Nightfall will send requests to this webhook endpoint with file scan results
@app.route("/ingest", methods=['POST'])
def ingest():
data = request.get_json(silent=True)
# validate webhook URL with challenge response
challenge = data.get("challenge")
if challenge:
return challenge
# challenge was passed, now validate the webhook payload
else:
# get details of the inbound webhook request for validation
request_signature = request.headers.get('X-Nightfall-Signature')
request_timestamp = request.headers.get('X-Nightfall-Timestamp')
request_data = request.get_data(as_text=True)
if nightfall.validate_webhook(request_signature, request_timestamp, request_data):
# check if any sensitive findings were found in the file, return if not
if not data["findingsPresent"]:
print("No sensitive data present!")
return "", 200
# there are sensitive findings in the file
# URL escape the temporary signed S3 URL where findings are available for download
escaped_url = urllib.parse.quote(data['findingsURL'])
# print the download URL and the URL where we can view the results in our web app
print(f"Sensitive data present. Findings available until {data['validUntil']}.\n\nDownload:\n{data['findingsURL']}\n\nView:\n{request.url_root}view?findings_url={escaped_url}\n")
return "", 200
else:
return "Invalid webhook", 500Restart your server so the changes propagate. We'll take a look at the console output of our webhook endpoint and explain what it means in the next section.
Now, we want to trigger a file scan request, so that Nightfall will scan the file and send a POST request to our /ingest webhook endpoint when the scan is complete. We'll write a simple script that sends a file to Nightfall to scan it for credit card numbers. Create a new file called scan.py.
First, we'll establish our dependencies, initialize the Nightfall client, and specify the filepath to the file we wish to scan as well as the webhook endpoint we created above. The filepath is a relative path to any file, in this case we are scanning the sample-pci-xs.csv file which is in the same directory as scan.py. This is a sample CSV file with 10 credit card numbers in it - you can download it in the tutorial's GitHub repo.
import os
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
nightfall = Nightfall() # reads API key from NIGHTFALL_API_KEY environment variable by default
filepath = "sample-pci-xs.csv" # sample file with sensitive data
webhook_url = f"{os.getenv('NIGHTFALL_SERVER_URL')}/ingest"Next, we will initiate the scan request to Nightfall, by specifying our filepath, webhook URL where the scan results should be posted, and our Detection Rule that specifies what sensitive data we are looking for.
In this simple example, we have specified an inline Detection Rule that detects Likely Credit Card Numbers. This Detection Rule is a simple starting point that just scratches the surface of the types of detection you can build with Nightfall. Learn more about building inline detection rules here or how to configure them in the Nightfall Dashboard.
scan_id, message = nightfall.scan_file(filepath,
webhook_url=webhook_url,
detection_rules=[ DetectionRule([
Detector(
min_confidence=Confidence.LIKELY,
nightfall_detector="CREDIT_CARD_NUMBER",
display_name="Credit Card Number"
)])
])
print(scan_id, message)The scan_id is useful for identifying your scan results later.
Let's run scan.py to trigger our file scan job.
Once Nightfall has finished scanning the file, we'll see our Flask server receive the request at our webhook endpoint (/ingest). In our code above, we parse the webhook payload, and print the following when there are sensitive findings:
Sensitive data present. Findings available until 2021-11-28T00:29:00.479700877Z.
Download:
https://files.nightfall.ai/d2160270-6b07-4304-b1ee-e7b98498be82.json?Expires=1638059340&Signature=AjSdNGlXWGXO0QGSi-lOoDBtbhJdLPE7IWXA7IaBCfLr~3X2IcZ1vavHF5iaEDaoZ-3etnZA4Nu8K8Dq8Kd81ShuX6Ze1o87mzb~8lD6WBk8hXShgW-TPBPpLMoBx2sA9TnefTqy94gI4ykt4tt1MttB67Cj69Miw-46cpFkgY9tannNPOF-90b3vlcS44PwqDUGrtTpQiN6WdsTT6LbpN1N92KbPJIRj3PkGwQW7VvpfM8L4wKmyVmVnRO3ixaW-mXXiOWk9rmfHP9UFMYnk99yaGHp4dZ1JfJiClci~Z8dBx288CrvXVjGUCXBJbdlwo6UrKQJCEk9i9vSbCpI2Q__&Key-Pair-Id=K24YOPZ1EKX0YC
View:
https://d3vwatchtower.ngrok.io/ingest/view?findings_url=https%3A//files.nightfall.ai/d2160270-6b07-4304-b1ee-e7b98498be82.json%3FExpires%3D1638059340%26Signature%3DAjSdNGlXWGXO0QGSi-lOoDBtbhJdLPE7IWXA7IaBCfLr~3X2IcZ1vavHF5iaEDaoZ-3etnZA4Nu8K8Dq8Kd81ShuX6Ze1o87mzb~8lD6WBk8hXShgW-TPBPpLMoBx2sA9TnefTqy94gI4ykt4tt1MttB67Cj69Miw-46cpFkgY9tannNPOF-90b3vlcS44PwqDUGrtTpQiN6WdsTT6LbpN1N92KbPJIRj3PkGwQW7VvpfM8L4wKmyVmVnRO3ixaW-mXXiOWk9rmfHP9UFMYnk99yaGHp4dZ1JfJiClci~Z8dBx288CrvXVjGUCXBJbdlwo6UrKQJCEk9i9vSbCpI2Q__%26Key-Pair-Id%3DK24YOPZ1EKX0YCIn our output, we are printing two URLs.
The first URL is provided to us by Nightfall. It is the temporary signed S3 URL that we can access to fetch the sensitive findings that Nightfall detected.
The second URL won't work yet, we'll implement it next. This URL a we constructed in our ingest() method above - the URL calls /view and passes the Findings URL above as a URL-escaped query parameter.
Let's add a method to our Flask server that opens this URL and displays the findings in a formatted table so that the results are easier to view than downloading them as JSON.
We'll do this by adding a view method that responds to GET requests to the /view route. The /view route will read the URL to the S3 Findings URL via a query parameter. It will then open the findings URL, parse it as JSON, pass the results to an HTML template, and display the results in a simple HTML table using Jinja. Jinja is a simple templating engine in Python.
Add the following to our Flask server in app.py:
# respond to GET requests at /view
# Users can access this page to view their file scan results in a table
@app.route("/view")
def view():
# get the findings URL from the query parameters
findings_url = request.args.get('findings_url')
if findings_url:
# download the findings from the findings URL and parse them as JSON
with urllib.request.urlopen(findings_url) as url:
data = json.loads(url.read().decode())
# render the view.html template and provide the findings object to display in the template
return render_template('view.html', findings=data['findings'])To display the findings in an HTML table, we'll create a new Flask template. Create a folder in your project directory called templates and add a new file within it called view.html.
Our template uses Jinja to iterate through our findings, and create a table row for each sensitive finding.
<!DOCTYPE HTML>
<html>
<head>
<title>File Scan Viewer</title>
<style>
table, th, td {
border: 1px solid black;
}
table {
width: 100%;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Detector</th>
<th>beforeContext</th>
<th>Finding</th>
<th>afterContext</th>
<th>byteRangeStart</th>
<th>byteRangeEnd</th>
<th>Confidence</th>
</tr>
</thead>
<tbody>
{% for finding in findings %}
<tr>
<td>{{ finding['detector']['name'] }}</td>
<td>{{ finding['beforeContext'] }}</td>
<td>{{ finding['finding'] }}</td>
<td>{{ finding['afterContext'] }}</td>
<td>{{ finding['location']['byteRange']['start'] }}</td>
<td>{{ finding['location']['byteRange']['start'] }}</td>
<td>{{ finding['confidence'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>Now, if we restart our Flask server, trigger a file scan request, and navigate to the "View" URL printed in the server logs, we should see a formatted table with our results! In fact, we can input any Nightfall-provided signed S3 URL (after URL-escaping it) in the findings_url parameter of the /view route to view it.
As a longtime Heroku user, I was initially inclined to write this tutorial with instructions to deploy our app on Heroku. However, new PaaS vendors have been emerging and I was curious to try them out and see how they compare to Heroku. One such vendor is Render, which is where we'll deploy our app.
Deploying our service on Render is straightforward. If you're familiar with Heroku, the process is quite similar. Once you've signed up or logged into Render (free), we'll do the following:
Create a new Web Service on Render, and permit Render to access your new repo.
Use the following values during creation:
Environment: Python
Build Command: pip install -r requirements.txt
Start Command: gunicorn app:app
Let's also set our environment variables during creation. These are the same values we set locally.
NIGHTFALL_API_KEY
NIGHTFALL_SIGNING_SECRETOnce Render has finished deploying, you'll get the base URL of your application. Set this as your NIGHTFALL_SERVER_URL locally and re-run scan.py - this time, the file scan request is served by your production Flask server running on Render!
export NIGHTFALL_SERVER_URL=https://your-app-url.onrender.com
python3 scan.pyTo confirm this, navigate to the Logs tab in your Render app console, you'll see the webhook's output of your file scan results:
Nov 26 04:29:06 PM Sensitive data present. Findings available until 2021-11-28T00:28:24.564972786Z.
Nov 26 04:29:06 PM
Nov 26 04:29:06 PM Download:
Nov 26 04:29:06 PM https://files.nightfall.ai/d6b6ee4f-d1a8-4fb6-b35a-cb6f88d58083.json?Expires=1638059304&Signature=hz1TN5UXjCGTxCxq~jT2wfuUWlj9Se-mWNL1K-tJhiAIXUg1FxJrCVP2iH1I4TNymFBuOnj5TTiLGpD8tZAKGm9J0lTHncZkaeaU8KZQ2j-~8qYQVlunNj019sqtTkMbVRfakzYzW-qWHEvLXN-PFcGYX05g3LZHvW802-lAVlM-WpGApw2u8BnzoY1pdWAxpJ0VIN1Zax4UuVeQBKieR7k8H9v9HdYYJlVGkVA5F9EzklLy99fyD8r4WR~jfqN5Fr1KceDtsxffC6MPuZ8nIIdSG5~tVtjCjgIjyh3IePPW1Wq-E8yZiVAhpDDbYX1wngUTwlAu~MU7N39vd8mlYQ__&Key-Pair-Id=K24YOPZ1EKX0YC
Nov 26 04:29:06 PM
Nov 26 04:29:06 PM View:
Nov 26 04:29:06 PM https://flask-file-scanner-example.onrender.com/view?findings_url=https%3A//files.nightfall.ai/d6b6ee4f-d1a8-4fb6-b35a-cb6f88d58083.json%3FExpires%3D1638059304%26Signature%3Dhz1TN5UXjCGTxCxq~jT2wfuUWlj9Se-mWNL1K-tJhiAIXUg1FxJrCVP2iH1I4TNymFBuOnj5TTiLGpD8tZAKGm9J0lTHncZkaeaU8KZQ2j-~8qYQVlunNj019sqtTkMbVRfakzYzW-qWHEvLXN-PFcGYX05g3LZHvW802-lAVlM-WpGApw2u8BnzoY1pdWAxpJ0VIN1Zax4UuVeQBKieR7k8H9v9HdYYJlVGkVA5F9EzklLy99fyD8r4WR~jfqN5Fr1KceDtsxffC6MPuZ8nIIdSG5~tVtjCjgIjyh3IePPW1Wq-E8yZiVAhpDDbYX1wngUTwlAu~MU7N39vd8mlYQ__%26Key-Pair-Id%3DK24YOPZ1EKX0YCNavigate to the View link above in your browser to verify that you can see the results formatted in a table on your production site.
Congrats, you've successfully created a file scanning server and deployed it in production! You're now ready to build more advanced business logic around your file scanner. Here are some ideas on how to extend this tutorial:
Use WebSockets to send a notification back from the webhook to the client that initiated the file scan request
Build a more advanced detection rule using pre-built or custom detectors
Add a user interface to add more interactive capabilities, for example allowing users to upload files or read files from URLs
Provide a list of arbitrary string data, and scan each item with the provided detectors to uncover sensitive information. Returns a list equal in size to the number of provided string payloads. The item at each list index will be a list of all matches for the provided detectors, or an empty list if no occurrences are found.
The request body of the /v3/scan endpoint
A list of UUIDs referring to policies to use to scan the request payload. Policies can be built in the Nightfall Dashboard. Maximum 1.
The text sample(s) you wish to scan. This data is passed as a string list, so you may choose to segment your text into multiple items for better granularity. The aggregate size of your text (summed across all items in the list) must not exceed 500 KB for any individual request, and the number of items in that list may not exceed 50,000.
Success
Invalid request payload
Authentication failure
Unprocessable request payload
Rate Limit Exceeded or Monthly Quota Exceeded
Internal Nightfall Error
POST /v3/scan HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 1595
{
"policyUUIDs": [
"text"
],
"policy": {
"detectionRuleUUIDs": [
"text"
],
"detectionRules": [
{
"name": "text",
"logicalOp": "ANY",
"detectors": [
{
"minNumFindings": 1,
"minConfidence": "VERY_UNLIKELY",
"detectorUUID": "text",
"displayName": "text",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "AMERICAN_BANKERS_CUSIP_ID",
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"wordList": {
"values": [
"text"
],
"isCaseSensitive": true
},
"contextRules": [
{
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"proximity": {
"windowBefore": 1,
"windowAfter": 1
},
"confidenceAdjustment": {
"fixedConfidence": "VERY_UNLIKELY"
}
}
],
"exclusionRules": [
{
"matchType": "PARTIAL",
"exclusionType": "REGEX",
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"wordList": {
"values": [
"text"
],
"isCaseSensitive": true
}
}
],
"redactionConfig": {
"maskConfig": {
"maskingChar": "text",
"charsToIgnore": [
"text"
],
"numCharsToLeaveUnmasked": 1,
"maskLeftToRight": true
},
"infoTypeSubstitutionConfig": {},
"substitutionConfig": {
"substitutionPhrase": "text"
},
"cryptoConfig": {
"publicKey": "text"
},
"removeFinding": true
},
"scope": "Content"
}
]
}
],
"contextBytes": 1,
"defaultRedactionConfig": {
"maskConfig": {
"maskingChar": "text",
"charsToIgnore": [
"text"
],
"numCharsToLeaveUnmasked": 1,
"maskLeftToRight": true
},
"infoTypeSubstitutionConfig": {},
"substitutionConfig": {
"substitutionPhrase": "text"
},
"cryptoConfig": {
"publicKey": "text"
},
"removeFinding": true
},
"alertConfig": {
"slack": {
"target": "text"
},
"email": {
"address": "text"
},
"url": {
"address": "text"
},
"siem": {
"address": "text",
"sensitiveHeaders": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"plainTextHeaders": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}
}
},
"payload": [
"text"
]
}{
"findings": [
[
{
"finding": "text",
"redactedFinding": "text",
"beforeContext": "text",
"afterContext": "text",
"detector": {
"name": "text",
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"subdetector": {
"name": "text",
"uuid": "123e4567-e89b-12d3-a456-426614174000"
}
},
"confidence": "VERY_UNLIKELY",
"location": {
"byteRange": {
"start": 1,
"end": 1
},
"codepointRange": {
"start": 1,
"end": 1
}
},
"redactedLocation": {
"byteRange": {
"start": 1,
"end": 1
},
"codepointRange": {
"start": 1,
"end": 1
}
}
}
]
],
"redactedPayload": [
"text"
]
}Creates a new file upload session. If this operation returns successfully, the ID returned as part of the response object shall be used to refer to the file in all subsequent upload and scanning operations.
the number of bytes representing the size of the file to-be-uploaded.
Success
Invalid request payload
Authentication failure
Rate Limit Exceeded or Monthly Quota Exceeded
Internal Nightfall Error
POST /v3/upload HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 19
{
"fileSizeBytes": 1
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"fileSizeBytes": 1,
"chunkSize": 1,
"mimeType": "text"
}Upload all bytes contained in the request body to the file identified by the ID in the path parameter.
a file ID returned from a previous file creation request
The numeric offset at which the bytes contained in the body should be written. This offset must be a multiple of the chunk size returned when the file upload was created.
The payload bytes to upload; the size of the request body must exactly match the chunkSize that was returned when the file upload was created.
Success
Invalid request payload
Authentication failure
Invalid File ID
Rate Limit Exceeded or Monthly Quota Exceeded
Internal Nightfall Error
PATCH /v3/upload/{fileId} HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
X-Upload-Offset: 1
Content-Type: application/octet-stream
Accept: */*
No content
Validates that all bytes of the file have been uploaded, and that the content type is supported by Nightfall.
a file ID returned from a previous file creation request
Success
Invalid request payload
Authentication failure
Invalid File ID
File Upload in Incorrect State
Rate Limit Exceeded or Monthly Quota Exceeded
Internal Nightfall Error
POST /v3/upload/{fileId}/finish HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"fileSizeBytes": 1,
"chunkSize": 1,
"mimeType": "text"
}Triggers a scan of the file identified by the provided fileID. As the underlying file might be arbitrarily large, this scan is conducted asynchronously. Results from the scan are delivered to the webhook URL provided in the request payload.
a file ID returned from a previous file creation request
the UUID of the Detection Policy to be used with this scan. Exactly one of this field or "policy" should be provided.
A string containing arbitrary metadata. Callers may opt to use this to help identify their input file upon receiving a webhook response. Maximum length 10 KB.
Success
Invalid request payload
Authentication failure
Invalid File ID
Incorrect File State
Unprocessable request payload
Rate Limit Exceeded or Monthly Quota Exceeded
Internal Nightfall Error
POST /v3/upload/{fileId}/scan HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 1672
{
"policyUUID": "123e4567-e89b-12d3-a456-426614174000",
"policy": {
"detectionRuleUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"detectionRules": [
{
"name": "text",
"logicalOp": "ANY",
"detectors": [
{
"minNumFindings": 1,
"minConfidence": "VERY_UNLIKELY",
"detectorUUID": "text",
"displayName": "text",
"detectorType": "NIGHTFALL_DETECTOR",
"nightfallDetector": "AMERICAN_BANKERS_CUSIP_ID",
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"wordList": {
"values": [
"text"
],
"isCaseSensitive": true
},
"contextRules": [
{
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"proximity": {
"windowBefore": 1,
"windowAfter": 1
},
"confidenceAdjustment": {
"fixedConfidence": "VERY_UNLIKELY"
}
}
],
"exclusionRules": [
{
"matchType": "PARTIAL",
"exclusionType": "REGEX",
"regex": {
"pattern": "text",
"isCaseSensitive": true
},
"wordList": {
"values": [
"text"
],
"isCaseSensitive": true
}
}
],
"redactionConfig": {
"maskConfig": {
"maskingChar": "text",
"charsToIgnore": [
"text"
],
"numCharsToLeaveUnmasked": 1,
"maskLeftToRight": true
},
"infoTypeSubstitutionConfig": {},
"substitutionConfig": {
"substitutionPhrase": "text"
},
"cryptoConfig": {
"publicKey": "text"
},
"removeFinding": true
},
"scope": "Content"
}
]
}
],
"alertConfig": {
"slack": {
"target": "text"
},
"email": {
"address": "text"
},
"url": {
"address": "text"
},
"siem": {
"address": "text",
"sensitiveHeaders": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"plainTextHeaders": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}
},
"defaultRedactionConfig": {
"maskConfig": {
"maskingChar": "text",
"charsToIgnore": [
"text"
],
"numCharsToLeaveUnmasked": 1,
"maskLeftToRight": true
},
"infoTypeSubstitutionConfig": {},
"substitutionConfig": {
"substitutionPhrase": "text"
},
"cryptoConfig": {
"publicKey": "text"
},
"removeFinding": true
},
"enableFileRedaction": true
},
"requestMetadata": "text"
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"message": "text"
}Fetch a list of posture events based on some filters
Unix timestamp in seconds, filters records created ≥ the value, defaults to -180 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Sort key and direction, defaults to descending order by creation time
TIME_DESCPossible values: The query containing filter clauses
Query structure and terminology
A query clause consists of a field followed by an operator followed by a value:
| term | value |
|---|---|
| clause | user_email:"[email protected]" |
| field | user_email |
| operator | : |
| value | [email protected] |
You can combine multiple query clauses in a search by separating them with a space.
Field types, substring matching, and numeric comparators
Every search field supports exact matching with a :. Certain fields such as user_email and user_name support substring matching.
Quotes
You may use quotation marks around string values. Quotation marks are required in case the value contains spaces. For example:
user_mail:[email protected]user_name:"John Doe"Special Characters
+ - && || ! ( ) { } [ ] ^ " ~ * ? : are special characters need to be escaped using \. For example:
(1+1):2 should be searched for using \(1\+1)\:2Search Syntax
The following table lists the syntax that you can use to construct a query.
| SYNTAX | USAGE | DESCRIPTION | EXAMPLES |
|---|---|---|---|
: |
field:value | Exact match operator (case insensitive) | state:"pending" returns records where the currency is exactly "PENDING" in a case-insensitive comparison |
(space) |
field1:value1 field2:value2 | The query returns only records that match both clauses | state:active slack.channel_name:general |
OR |
field:(value1 OR value2) | The query returns records that match either of the values (case insensitive) | state:(active OR pending) |
Query Fields
| param | description |
|---|---|
| event_id | the unique identifier of the posture event to filter on |
| integration_name | the name of the integration to filter on |
| state | the state of the event to filter on (active, pending, resolved, expired) |
| event_type | the type of posture event to filter on |
| actor_name | the name of the actor who performed the action to filter on |
| actor_email | the email of the actor who performed the action to filter on |
| user_name | the username of the user to filter on (backward compatibility) |
| user_email | the email of the user to filter on (backward compatibility) |
| notes | the comment or notes associated with the event to filter on |
| policy_id | the unique identifier of the policy to filter on |
| policy_name | the name of the policy to filter on |
| resource_id | the identifier of the resource to filter on |
| resource_name | the name of the resource to filter on |
| resource_owner_name | the name of the resource owner to filter on |
| resource_owner_email | the email of the resource owner to filter on |
| resource_content_type | the content type of the resource to filter on |
| endpoint.device_id | the device identifier for endpoint events to filter on |
| endpoint.machine_name | the machine name for endpoint events to filter on |
| gdrive.permission | the permission setting for Google Drive files to filter on |
| gdrive.shared_internal_email | the internal emails with which the file is shared to filter on |
| gdrive.shared_external_email | the external emails with which the file is shared to filter on |
| gdrive.drive | the Google Drive name to filter on |
| gdrive.file_owner | the owner of the Google Drive file to filter on |
| gdrive.label_name | the label name applied to Google Drive files to filter on |
| salesforce.report.scope | the scope of the Salesforce report to filter on |
| salesforce.report.event_source | the event source of the Salesforce report to filter on |
| salesforce.report.source_ip | the source IP address of the Salesforce report to filter on |
| salesforce.report.session_level | the session level of the Salesforce report to filter on |
| salesforce.report.operation | the operation type of the Salesforce report to filter on |
| salesforce.report.description | the description of the Salesforce report to filter on |
| salesforce.file.source_ip | the source IP address for Salesforce file events to filter on |
| salesforce.file.session_level | the session level for Salesforce file events to filter on |
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/events/search?query=text HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"events": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"integration": "text",
"createdAt": 1,
"state": "text",
"eventType": "text",
"policyUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"assetsCount": 1,
"userInfo": {
"username": "text",
"userEmail": "[email protected]",
"userProfileLink": "https://example.com",
"deviceId": "text",
"machineName": "text",
"isExternal": true
},
"appInfo": {
"id": "text",
"name": "text"
}
}
],
"nextPageToken": "text"
}Fetch a list of posture events for a period
Unix timestamp in seconds, filters records created ≥ the value, defaults to -90 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/events HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"events": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"integration": "text",
"createdAt": 1,
"state": "text",
"eventType": "text",
"policyUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"assetsCount": 1,
"userInfo": {
"username": "text",
"userEmail": "[email protected]",
"userProfileLink": "https://example.com",
"deviceId": "text",
"machineName": "text",
"isExternal": true
},
"appInfo": {
"id": "text",
"name": "text"
}
}
],
"nextPageToken": "text"
}Fetch an posture event details by ID
The UUID of the event to fetch
Successful response
Invalid request parameters
Authentication failure
Event does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/events/{eventId} HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"assets": {
"id": "text",
"name": "text",
"path": "text",
"sizeBytes": 1,
"mimetype": "text",
"owner": {
"id": "text",
"email": "[email protected]",
"comment": "text",
"metadata": {
"gdrive": {
"userBelongsToGroups": [
"text"
],
"isAdmin": true,
"isSuspended": true,
"createdAt": 1
},
"salesforce": {}
}
},
"comment": "text",
"ddrViolationIDs": [],
"metadata": {
"gdrive": {
"fileID": "text",
"fileName": "text",
"fileSize": "text",
"fileLink": "text",
"permissionSetting": "text",
"sharingExternalUsers": [
"text"
],
"sharingInternalUsers": [
"text"
],
"canViewersDownload": true,
"fileOwner": "text",
"isInTrash": true,
"createdAt": 1,
"updatedAt": 1,
"drive": "text",
"labels": [
"text"
],
"filePermissionType": "text"
},
"salesforce": {
"resourceType": "text",
"fileResourceMetadata": {
"fileAction": "text",
"sourceIP": "text",
"sessionLevel": "text"
},
"reportResourceMetadata": {
"description": "text",
"displayEntityFields": [
"text"
],
"dashboardName": "text",
"scope": "text",
"operation": "text",
"recordCount": 1,
"queriedEntities": [
"text"
],
"groupedColumnHeaders": [
"text"
],
"columnCount": 1,
"processedRowCount": 1,
"sourceIP": "text",
"eventSource": "text",
"sessionLevel": "text"
},
"bulkApiResourceMetadata": {
"query": "text",
"eventIdentifier": "text",
"sourceIP": "text",
"sessionKey": "text",
"sessionLevel": "text"
}
}
}
},
"actor": {
"id": "text",
"email": "[email protected]",
"comment": "text",
"metadata": {
"gdrive": {
"userBelongsToGroups": [
"text"
],
"isAdmin": true,
"isSuspended": true,
"createdAt": 1
},
"salesforce": {}
}
},
"events": {
"type": "PERMISSION_CHANGE",
"timestamp": 1,
"metadata": {
"gdrive": {
"originatingAppId": "text",
"originatingAppName": "text",
"isClientSyncEvent": true
},
"salesforce": {
"sourceIP": "text",
"sessionLevel": "text",
"sessionKey": "text",
"sfUserId": "text"
}
},
"assetIDs": []
}
}Fetch the activity feed for a specific posture event
The UUID of the posture event
Number of activity items to fetch in one page
50Unix timestamp in seconds, filters activity created > the value
Unix timestamp in seconds, filters activity created < the value
Whether to sort results in descending order (default false)
falseCursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Event does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/events/{eventId}/activity HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"message": "text",
"timestamp": 1,
"type": "CREATION",
"data": {
"action": "ACKNOWLEDGE",
"userUUID": "123e4567-e89b-12d3-a456-426614174000",
"userName": "text",
"userEmail": "text",
"receiverEmail": "text",
"actionLogData": {
"applyLabelsActionLogData": {
"activityType": "LABELS_SUCCESSFULLY_APPLIED",
"labels": [
"text"
],
"labelUpdatesOnResource": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"failureReason": "text"
},
"setExpirationActionLogData": {
"expirationTime": 1
},
"revokeAccessActionLogData": {
"revokedEmails": [
"text"
],
"unRevokedEmails": [
"text"
]
},
"notifyEmailActionLogData": {
"receiverEmail": "text"
},
"genericActionLogData": {
"additionalContext": "text"
}
}
}
}
],
"nextPageToken": "text"
}Fetch the activity history for a specific asset
The ID of the asset to fetch activities for
Unix timestamp in seconds, filters activities created ≥ the value
Unix timestamp in seconds, filters activities created < the value
Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/asset/activity?assetID=text&rangeStart=1&rangeEnd=1 HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"type": "DOWNLOAD",
"userEmail": "[email protected]",
"eventTime": 1,
"assetNames": [
"text"
],
"metadata": {
"downloadEventMetadata": {
"source": "text",
"fileName": "text"
},
"browserUploadMetadata": {
"domain": "text",
"fileName": "text"
},
"cloudSyncMetadata": {
"cloudApp": "text",
"fileName": "text"
},
"clipboardMetadata": {
"browserMetadata": {
"domain": "text"
}
}
}
}
],
"nextPageToken": "text"
}Fetch the activity history for a specific actor
The Nightfall ID of the actor to fetch activities for
Unix timestamp in seconds, filters activities created ≥ the value
Unix timestamp in seconds, filters activities created < the value
Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /posture/v1/actor/activity?actorID=text&rangeStart=1&rangeEnd=1 HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"type": "DOWNLOAD",
"userEmail": "[email protected]",
"eventTime": 1,
"assetNames": [
"text"
],
"metadata": {
"downloadEventMetadata": {
"source": "text",
"fileName": "text"
},
"browserUploadMetadata": {
"domain": "text",
"fileName": "text"
},
"cloudSyncMetadata": {
"cloudApp": "text",
"fileName": "text"
},
"clipboardMetadata": {
"browserMetadata": {
"domain": "text"
}
}
}
}
],
"nextPageToken": "text"
}Endpoint data loss prevention (DLP) discovers, classifies, and protects sensitive data - like PII, credit card numbers, and secrets - that proliferates onto endpoint devices, like your computer or EC2 machines. This is a way to help keep data safe, so that you can detect and stop occurrences of data exfiltration. Our endpoint DLP application will be composed of two core services that will run locally. The first service will monitor for file system events using the package in Python. When a file system event is triggered, such as when a file is created or modified, the service will send the file to to be scanned for sensitive data. The second service is a webhook server that will receive scan results from Nightfall, parse the sensitive findings, and write them to a CSV file as output. You'll build familiarity with the following tools and frameworks:
Python
Flask
Nightfall
Ngrok
Watchdog
Before we get started on our implementation, start by familiarizing yourself with with Nightfall, so you're acquainted with the flow we are implementing.
In a nutshell, file scanning is done asynchronously by Nightfall; after you upload a file to Nightfall and trigger the scan, we perform the scan in the background. When the scan completes, Nightfall delivers the results to you by requesting your webhook server. This asynchronous behavior allows Nightfall to scan files of varying sizes and complexities without requiring you to hold open a long synchronous request, or continuously poll for updates. The impact of this pattern is that you need a webhook endpoint that can receive inbound notifications from Nightfall when scans are completed - that's one of the two services we are building in this tutorial.
You can fork the sample repo and view the complete code , or follow along below. If you're starting from scratch, create a new GitHub repository. This tutorial was developed on a Mac and assumes that's the endpoint operating system you're running, however, this tutorial should work across operating systems with minor modifications. For example, you may wish to extend this tutorial by running endpoint DLP on an EC2 machine to monitor your production systems.
First, let's start by installing our dependencies. We'll be using Nightfall for data classification, the web framework in Python, for monitoring file system events, and as our web server. Create requirements.txt and add the following to the file:
Then run pip install -r requirements.txt to do the installation.
Next, we'll need our Nightfall API Key and Webhook Signing Secret; the former authenticates us to the Nightfall API, while the latter authenticates that incoming webhooks are originating from Nightfall. You can retrieve your API Key and Webhook Signing Secret from the Nightfall . Complete the Nightfall Quickstart for a more detailed walk-through. for a free Nightfall account if you don't have one.
These values are unique to your account and should be kept safe. This means that we will store them as environment variables and should not store them directly in code or commit them into version control. If these values are ever leaked, be sure to visit the Nightfall Dashboard to re-generate new values for these secrets.
Watchdog is a Python module that watches for file system events. Create a file called scanner.py. We'll start by importing our dependencies and setting up a basic event handler. This event handler responds to file change events for file paths that match a given set of regular expressions (regexes). In this case, the .* indicates we are matching on any file path - we'll customize this a bit later. When a file system event is triggered, we'll print a line to the console.
Run python scanner.py and you'll notice lots of lines getting printed to the console. These are all the files that are getting created and changed on your machine in real-time. You'll notice that your operating system and the apps you're running are constantly writing, modifying, and deleting files on disk!
Next, we'll update our event handler so that instead of simply printing to the console, we are sending the file to Nightfall to be scanned. We will initiate the scan request to Nightfall, by specifying the file path of the changed/created file, a webhook URL where the scan results should be sent, and our Detection Rule that specifies what sensitive data we are looking for. If the file scan is initiated successfully, we'll print the corresponding Upload ID that Nightfall provides us to the console. This ID will be useful later when identifying scan results.
Here's our complete scanner.py, explained further below:
We can't run this just yet, since we need to set our webhook URL, which is currently reading from an environment variable that we haven't set yet. We'll create our webhook server and set the webhook URL in the next set of steps.
In this example, we have specified an inline Detection Rule that detects Likely Credit Card Numbers, Social Security Numbers, and API Keys. This Detection Rule is a simple starting point that just scratches the surface of the types of detection you can build with Nightfall. Learn more about building inline detection rules here or how to configure them in the Nightfall .
Also note that we've updated our regex from .* to a set of file paths on Macs that commonly contain user generated files - the Desktop, Documents, and Downloads folders:
You can customize these regexes to whatever file paths are of interest to you. Another option is to write a catch-all regex that ignores/excludes paths to config and temp files:
Next, we'll set up our Flask webhook server, so we can receive file scanning results from Nightfall. Create a file called app.py. We'll start by importing our dependencies and initializing the Flask and Nightfall clients:
Next, we'll add our first route, which will display "Hello World" when the client navigates to /ping simply as a way to validate things are working:
In a second command line window, run gunicorn app:app on the command line to fire up your server, and navigate to your local server in your web browser. You'll see where the web browser is hosted in the Gunicorn logs, typically it will be 127.0.0.1:8000 aka localhost:8000.
To expose our local webhook server via a public tunnel that Nightfall can send requests to, we'll use ngrok. Download and install ngrok via their quickstart documentation . We'll create an ngrok tunnel as follows:
After running this command, ngrok will create a tunnel on the public internet that redirects traffic from their site to your local machine. Copy the HTTPS tunnel endpoint that ngrok has created: we can use this as the webhook URL when we trigger a file scan.
Let's set this HTTPS endpoint as a local environment variable so we can reference it later:
Before we send a file scan request to Nightfall, let's implement our incoming webhook endpoint, so that when Nightfall finishes scanning a file, it can successfully send the sensitive findings to us.
First, what does it mean to have findings? If a file has findings, this means that Nightfall identified sensitive data in the file that matched the detection rules you configured. For example, if you told Nightfall to look for credit card numbers, any substring from the request payload that matched our credit card detector would constitute sensitive findings.
We'll host our incoming webhook at /ingest with a POST method.
Nightfall will POST to the webhook endpoint, and in the inbound payload, Nightfall will indicate if there are sensitive findings in the file, and provide a link where we can access the sensitive findings as JSON.
We'll validate the inbound webhook from Nightfall, retrieve the JSON findings from the link provided, and write the findings to a CSV file. First, let's initialize our CSV file where we will write results, and add our /ingest POST method.
You'll notice that when there are sensitive findings, we call the output_results() method. Let's write that next. In output_results(), we are going to parse the findings and write them as rows into our CSV file.
Restart your server so the changes propagate. We'll take a look at the console and CSV output of our webhook endpoint in the next section.
In our previous command line window, we can now turn our attention back to scanner.py. We now have our webhook URL so let's set it here as well and run our scanner.
To trigger a file scan event, download the following . Assuming it automatically downloads to your Downloads folder, this should immediately trigger a file change event and you'll see console log output! If not, you can also download the file with curl into a location that matches your event handler's regex we set earlier.
You'll see the following console output from scanner.py:
And the following console output from our webhook server:
And the following sensitive findings written to results.csv:
Each row in the output CSV will correspond to a sensitive finding. Each row will have the following fields, which you can customize in app.py: the upload ID provided by Nightfall, an incrementing index, timestamp, characters before the sensitive finding (for context), the sensitive finding itself, characters after the sensitive finding (for context), the confidence level of the detection, the byte range location (character indicies) of the sensitive finding in its parent file, and the corresponding detection rules that flagged the sensitive finding.
Note that you may also see events for system files like .DS_Store or errors corresponding to failed attempts to scan temporary versions of files. This is because doing things like downloading a file can trigger multiple file modification events. As an extension to this tutorial, you could consider filtering those out further, though they shouldn't impact our ability to scan files of interest.
If we leave these services running, we'll continue to monitor files for sensitive data and appending to our results CSV when sensitive findings are discovered!
We can run both of our services in the background nohup so that we don't need to leave two command line tabs open indefinitely. We'll pipe console output to log files so that we can always reference the application's output or determine if the services crashed for any reason.
This will return the corresponding process IDs - we can always check on these later with the ps command.
This post is simply of a proof of concept version of endpoint DLP. Building a production-grade endpoint DLP application will have additional complexity and functionality. However, the detection engine is one of the biggest components of an endpoint DLP system, and this example should give you a sense of how easy it is to integrate with Nightfall's APIs and the power of Nightfall's detection engine.
Here are few ideas on how you can extend upon this service further:
Run the scanner on EC2 machines to scan your production machines in real-time
Respond to more system events like I/O of USB drives and external ports
Implement remediation actions like end-user notifications or file deletion
Redact the sensitive findings prior to writing them to the results file
Store the results in the cloud for central reporting
Package in an executable so the application can be run easily
Scan all files on disk on the first boot of the application
nightfall
Flask
Gunicorn
watchdogexport NIGHTFALL_API_KEY=<your_key_here>
export NIGHTFALL_SIGNING_SECRET=<your_secret_here>import os
import time
from watchdog.observers import Observer
from watchdog.events import RegexMatchingEventHandler
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
class MyHandler(RegexMatchingEventHandler):
# event handler callback that is called when a file is modified (created or changed)
def on_modified(self, event):
print(f'Event type: {event.event_type} | Path: {event.src_path}')
if __name__ == "__main__":
regexes = [ ".*" ]
# register event handler to monitor file paths that match our regex
event_handler = MyHandler(regexes)
observer = Observer()
observer.schedule(event_handler, path='', recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()Event type: modified | Path: /Users/myuser/Library/Caches
Event type: modified | Path: /Users/myuser/Library/Caches/com.apple.nsservicescache.plist
Event type: modified | Path: /Users/myuser/Library/Caches
Event type: modified | Path: /Users/myuser/Library/Caches/Google/Chrome/Default/Cache
Event type: modified | Path: /private/tmp
Event type: modified | Path: /Users/myuser/Library/Preferences/ContextStoreAgent.plist
Event type: modified | Path: /private/tmp
Event type: modified | Path: /Users/myuser/Library/Assistant
Event type: modified | Path: /Users/myuser/Library/Assistant/SyncSnapshot.plist
...import os
import time
from watchdog.observers import Observer
from watchdog.events import RegexMatchingEventHandler
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
class MyHandler(RegexMatchingEventHandler):
def scan_file(self, filepath):
nightfall = Nightfall() # reads API key from NIGHTFALL_API_KEY environment variable by default
webhook_url = f"{os.getenv('NIGHTFALL_SERVER_URL')}/ingest" # webhook server we'll create
try:
scan_id, message = nightfall.scan_file(
filepath,
webhook_url=webhook_url,
# detection rule to detect credit card numbers, SSNs, and API keys
detection_rules=[ DetectionRule([
Detector(
min_confidence=Confidence.LIKELY,
nightfall_detector="CREDIT_CARD_NUMBER",
display_name="Credit Card Number"),
Detector(
min_confidence=Confidence.LIKELY,
nightfall_detector="US_SOCIAL_SECURITY_NUMBER",
display_name="US Social Security Number"),
Detector(
min_confidence=Confidence.LIKELY,
nightfall_detector="API_KEY",
display_name="API Key")
])
])
return scan_id, message
except Exception as err:
print(f"Error processing {filepath} | {err}")
return None, None
def on_modified(self, event):
# scan file with Nightfall
scan_id, message = self.scan_file(event.src_path)
if scan_id:
print(f"Scan initiated | Path {event.src_path} | UploadID {scan_id}")
print(f'Event type: {event.event_type} | Path: {event.src_path}')
if __name__ == "__main__":
regexes = [ ".*/Downloads/.*", ".*/Desktop/.*", ".*/Documents/.*" ]
# register event handler to monitor file paths that match our regexes
event_handler = MyHandler(regexes)
observer = Observer()
observer.schedule(event_handler, path='', recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()regexes = [ ".*/Downloads/.*", ".*/Desktop/.*", ".*/Documents/.*" ]regexes = [ "(?!/opt/|.*/Library/|.*/private/|/System/|/Applications/|/usr/).*" ]import os
from flask import Flask, request, render_template
from nightfall import Confidence, DetectionRule, Detector, RedactionConfig, MaskConfig, Nightfall
from datetime import datetime, timedelta
import urllib.request, urllib.parse, json
import csv
app = Flask(__name__)
nightfall = Nightfall(
key=os.getenv('NIGHTFALL_API_KEY'),
signing_secret=os.getenv('NIGHTFALL_SIGNING_SECRET')
)@app.route("/ping")
def ping():
return "Hello World", 200[2021-11-26 14:22:53 -0800] [61196] [INFO] Starting gunicorn 20.1.0
[2021-11-26 14:22:53 -0800] [61196] [INFO] Listening at: http://127.0.0.1:8000 (61196)
[2021-11-26 14:22:53 -0800] [61196] [INFO] Using worker: sync
[2021-11-26 14:22:53 -0800] [61246] [INFO] Booting worker with pid: 61246./ngrok http 8000Account Nightfall Example
Version 2.3.40
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://3ecedafba368.ngrok.io -> http://localhost:8000
Forwarding https://3ecedafba368.ngrok.io -> http://localhost:8000export NIGHTFALL_SERVER_URL=https://3ecedafba368.ngrok.io# create CSV where sensitive findings will be written
headers = ["upload_id", "#", "datetime", "before_context", "finding", "after_context", "detector", "confidence", "loc", "detection_rules"]
with open(f"results.csv", 'a') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(headers)
# respond to POST requests at /ingest
# Nightfall will send requests to this webhook endpoint with file scan results
@app.route("/ingest", methods=['POST'])
def ingest():
data = request.get_json(silent=True)
# validate webhook URL with challenge response
challenge = data.get("challenge")
if challenge:
return challenge
# challenge was passed, now validate the webhook payload
else:
# get details of the inbound webhook request for validation
request_signature = request.headers.get('X-Nightfall-Signature')
request_timestamp = request.headers.get('X-Nightfall-Timestamp')
request_data = request.get_data(as_text=True)
if nightfall.validate_webhook(request_signature, request_timestamp, request_data):
# check if any sensitive findings were found in the file, return if not
if not data["findingsPresent"]:
print("No sensitive data present!")
return "", 200
# there are sensitive findings in the file
output_results(data)
return "", 200
else:
return "Invalid webhook", 500def output_results(data):
findings_url = data['findingsURL']
# open findings URL provided by Nightfall to access findings
with urllib.request.urlopen(findings_url) as url:
findings = json.loads(url.read().decode())
findings = findings['findings']
print(f"Sensitive data found, outputting {len(findings)} finding(s) to CSV | UploadID {data['uploadID']}")
table = []
# loop through findings JSON, get relevant finding metadata, write each finding as a row into output CSV
for i, finding in enumerate(findings):
row = [
data['uploadID'],
i+1,
datetime.now(),
repr(finding['beforeContext']),
repr(finding['finding']),
repr(finding['afterContext']),
finding['detector']['name'],
finding['confidence'],
finding['location']['byteRange'],
finding['matchedDetectionRules']
]
table.append(row)
with open(f"results.csv", 'a') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(row)
returnexport NIGHTFALL_SERVER_URL=https://3ecedafba368.ngrok.io
python scanner.pycurl https://raw.githubusercontent.com/nightfallai/dlp-sample-data/main/sample-pci.csv > ~/Downloads/sample-pci.csvEvent type: modified | Path: /Users/myuser/Downloads/sample-pci.csv
Scan initiated | Path /Users/myuser/Downloads/sample-pci.csv | UploadID c23fdde2-5e98-4183-90b0-31e2cdd20ac0Sensitive data found, outputting 10 finding(s) to CSV | UploadID ac6a4a9d-a7b9-4a78-810d-8a66f7644704upload_id,#,datetime,before_context,finding,after_context,detector,confidence,loc,detection_rules
ac6a4a9d-a7b9-4a78-810d-8a66f7644704,1,2021-12-04 22:12:21.039602,'Name\tCredit Card\nRep. Viviana Hintz\t','5433-9502-3725-7862','\nEloisa Champlin\t3457-389808-83234\nOmega',Credit Card Number,VERY_LIKELY,"{'start': 36, 'end': 55}",[]
...nohup python -u scanner.py > scanner.log &
nohup gunicorn app:app > server.log &Update a policy user scope, define inclusion/exclusion rule for users using user emails. Only supports gDrive policies, separates internal or external users based on google domains registered in Nightfall.
The UUID of the policy to update
Successful response (processed immediately)
Invalid request parameters
Authentication failure
Operation prohibited on the policy
Policy not found
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
POST /policy/v1/{policyID}/scope/users HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 136
{
"add": {
"include": [
"[email protected]"
],
"exclude": [
"[email protected]"
]
},
"delete": {
"include": [
"[email protected]"
],
"exclude": [
"[email protected]"
]
}
}{
"includedUsers": [
"text"
],
"excludedUsers": [
"text"
]
}Update a policy domain scope, define inclusion/exclusion rule for domains. Only supports gDrive policies.
The UUID of the policy to update
Successful response (processed immediately)
Invalid request parameters
Authentication failure
Operation prohibited on the policy
Policy not found
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
POST /policy/v1/{policyID}/scope/domains HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 96
{
"add": {
"include": [
"text"
],
"exclude": [
"text"
]
},
"delete": {
"include": [
"text"
],
"exclude": [
"text"
]
}
}{
"includedDomains": [
"text"
],
"excludedDomains": [
"text"
]
}Fetch a list of exfiltration events based on some filters
Unix timestamp in seconds, filters records created ≥ the value, defaults to -180 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Sort key and direction, defaults to descending order by creation time
TIME_DESCPossible values: The query containing filter clauses
Query structure and terminology
A query clause consists of a field followed by an operator followed by a value:
| term | value |
|---|---|
| clause | user_email:"[email protected]" |
| field | user_email |
| operator | : |
| value | [email protected] |
You can combine multiple query clauses in a search by separating them with a space.
Field types, substring matching, and numeric comparators
Every search field supports exact matching with a :. Certain fields such as user_email and user_name support substring matching.
Quotes
You may use quotation marks around string values. Quotation marks are required in case the value contains spaces. For example:
user_mail:[email protected]user_name:"John Doe"Special Characters
+ - && || ! ( ) { } [ ] ^ " ~ * ? : are special characters need to be escaped using \. For example:
(1+1):2 should be searched for using \(1\+1)\:2Search Syntax
The following table lists the syntax that you can use to construct a query.
| SYNTAX | USAGE | DESCRIPTION | EXAMPLES |
|---|---|---|---|
: |
field:value | Exact match operator (case insensitive) | state:"pending" returns records where the currency is exactly "PENDING" in a case-insensitive comparison |
(space) |
field1:value1 field2:value2 | The query returns only records that match both clauses | state:active slack.channel_name:general |
OR |
field:(value1 OR value2) | The query returns records that match either of the values (case insensitive) | state:(active OR pending) |
Query Fields
| param | description |
|---|---|
| event_id | the unique identifier of the exfiltration event to filter on |
| integration_name | the name of the integration to filter on |
| state | the state of the event to filter on (active, pending, resolved, expired) |
| event_type | the type of exfiltration event to filter on |
| actor_name | the name of the actor who performed the action to filter on |
| actor_email | the email of the actor who performed the action to filter on |
| user_name | the username of the user to filter on (backward compatibility) |
| user_email | the email of the user to filter on (backward compatibility) |
| notes | the comment or notes associated with the event to filter on |
| policy_id | the unique identifier of the policy to filter on |
| policy_name | the name of the policy to filter on |
| resource_id | the identifier of the resource to filter on |
| resource_name | the name of the resource to filter on |
| resource_owner_name | the name of the resource owner to filter on |
| resource_owner_email | the email of the resource owner to filter on |
| resource_content_type | the content type of the resource to filter on |
| endpoint.device_id | the device identifier for endpoint events to filter on |
| endpoint.machine_name | the machine name for endpoint events to filter on |
| gdrive.permission | the permission setting for Google Drive files to filter on |
| gdrive.shared_internal_email | the internal emails with which the file is shared to filter on |
| gdrive.shared_external_email | the external emails with which the file is shared to filter on |
| gdrive.drive | the Google Drive name to filter on |
| gdrive.file_owner | the owner of the Google Drive file to filter on |
| gdrive.label_name | the label name applied to Google Drive files to filter on |
| salesforce.report.scope | the scope of the Salesforce report to filter on |
| salesforce.report.event_source | the event source of the Salesforce report to filter on |
| salesforce.report.source_ip | the source IP address of the Salesforce report to filter on |
| salesforce.report.session_level | the session level of the Salesforce report to filter on |
| salesforce.report.operation | the operation type of the Salesforce report to filter on |
| salesforce.report.description | the description of the Salesforce report to filter on |
| salesforce.file.source_ip | the source IP address for Salesforce file events to filter on |
| salesforce.file.session_level | the session level for Salesforce file events to filter on |
| last_actioned_by | the entity that performed the last action on the violation, can be one of NIGHTFALL, ADMIN or END_USER |
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/events/search?query=text HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"events": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"integration": "text",
"createdAt": 1,
"state": "text",
"eventType": "text",
"policyUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"assetsCount": 1,
"userInfo": {
"username": "text",
"userEmail": "[email protected]",
"userProfileLink": "https://example.com",
"deviceId": "text",
"machineName": "text",
"isExternal": true
},
"appInfo": {
"id": "text",
"name": "text"
}
}
],
"nextPageToken": "text"
}Fetch a list of exfiltration events for a period
Unix timestamp in seconds, filters records created ≥ the value, defaults to -90 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/events HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"events": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"integration": "text",
"createdAt": 1,
"state": "text",
"eventType": "text",
"policyUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"assetsCount": 1,
"userInfo": {
"username": "text",
"userEmail": "[email protected]",
"userProfileLink": "https://example.com",
"deviceId": "text",
"machineName": "text",
"isExternal": true
},
"appInfo": {
"id": "text",
"name": "text"
}
}
],
"nextPageToken": "text"
}Fetch an exfiltration event details by ID
The UUID of the event to fetch
Successful response
Invalid request parameters
Authentication failure
Event does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/events/{eventId} HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"assets": [
{
"id": "text",
"name": "text",
"path": "text",
"sizeBytes": 1,
"mimetype": "text",
"owner": {
"id": "text",
"email": "[email protected]",
"comment": "text",
"metadata": {
"gdrive": {
"userBelongsToGroups": [
"text"
],
"isAdmin": true,
"isSuspended": true,
"createdAt": 1
},
"salesforce": {},
"endpointAgent": {
"deviceID": "text",
"machineName": "text"
}
}
},
"comment": "text",
"ddrViolationIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"metadata": {
"gdrive": {
"fileID": "text",
"fileName": "text",
"fileSize": "text",
"fileLink": "text",
"permissionSetting": "text",
"sharingExternalUsers": [
"text"
],
"sharingInternalUsers": [
"text"
],
"canViewersDownload": true,
"fileOwner": "text",
"isInTrash": true,
"createdAt": 1,
"updatedAt": 1,
"drive": "text",
"labels": [
"text"
],
"filePermissionType": "text"
},
"salesforce": {
"resourceType": "text",
"fileResourceMetadata": {
"fileAction": "text",
"sourceIP": "text",
"sessionLevel": "text"
},
"reportResourceMetadata": {
"description": "text",
"displayEntityFields": [
"text"
],
"dashboardName": "text",
"scope": "text",
"operation": "text",
"recordCount": 1,
"queriedEntities": [
"text"
],
"groupedColumnHeaders": [
"text"
],
"columnCount": 1,
"processedRowCount": 1,
"sourceIP": "text",
"eventSource": "text",
"sessionLevel": "text"
},
"bulkApiResourceMetadata": {
"query": "text",
"eventIdentifier": "text",
"sourceIP": "text",
"sessionKey": "text",
"sessionLevel": "text"
}
},
"endpointAgent": {
"medium": "EXFIL_MEDIUM_USB",
"mediumName": "text",
"user": "text"
}
}
}
],
"actor": {
"id": "text",
"email": "[email protected]",
"comment": "text",
"metadata": {
"gdrive": {
"userBelongsToGroups": [
"text"
],
"isAdmin": true,
"isSuspended": true,
"createdAt": 1
},
"salesforce": {},
"endpointAgent": {
"deviceID": "text",
"machineName": "text"
}
}
},
"events": [
{
"type": "DOWNLOAD",
"timestamp": 1,
"metadata": {
"endpointAgent": {
"endpointBrowserUploadMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text",
"uploadStartTime": 1,
"uploadEndTime": 1,
"fileName": "text",
"originMetadata": [
{
"timestamp": 1,
"browserDownloadMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text",
"downloadStartTime": 1,
"downloadEndTime": 1
},
"clipboardCopyMetadata": {
"contentType": "CCT_TEXT",
"browserMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text"
}
}
}
]
},
"endpointCloudSyncMetadata": {
"app": "text",
"accountType": "text",
"accountName": "text",
"email": "text",
"destinationFilePath": "text",
"uploadStartTime": 1,
"uploadEndTime": 1,
"fileName": "text"
},
"endpointClipboardMetadata": {
"contentType": "text",
"originMetadata": [
{
"timestamp": 1,
"browserDownloadMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text",
"downloadStartTime": 1,
"downloadEndTime": 1
},
"clipboardCopyMetadata": {
"contentType": "CCT_TEXT",
"browserMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text"
}
}
}
],
"destinationMetadata": {
"browserMetadata": {
"browserName": "text",
"browserVersion": "text",
"domain": "text",
"browserTabURL": "text",
"browserTabTitle": "text"
}
}
}
},
"gdrive": {
"originatingAppId": "text",
"originatingAppName": "text",
"isClientSyncEvent": true
},
"salesforce": {
"sourceIP": "text",
"sessionLevel": "text",
"sessionKey": "text",
"sfUserId": "text"
}
},
"assetIDs": [
"text"
]
}
]
}Fetch the activity feed for a specific exfiltration event
The UUID of the exfiltration event
Number of activity items to fetch in one page
50Unix timestamp in seconds, filters activity created > the value
Unix timestamp in seconds, filters activity created < the value
Whether to sort results in descending order (default false)
falseCursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Event does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/events/{eventId}/activity HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"message": "text",
"timestamp": 1,
"type": "CREATION",
"data": {
"action": "ACKNOWLEDGE",
"userUUID": "123e4567-e89b-12d3-a456-426614174000",
"userName": "text",
"userEmail": "text",
"receiverEmail": "text",
"actionLogData": {
"applyLabelsActionLogData": {
"activityType": "LABELS_SUCCESSFULLY_APPLIED",
"labels": [
"text"
],
"labelUpdatesOnResource": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"failureReason": "text"
},
"setExpirationActionLogData": {
"expirationTime": 1
},
"revokeAccessActionLogData": {
"revokedEmails": [
"text"
],
"unRevokedEmails": [
"text"
]
},
"notifyEmailActionLogData": {
"receiverEmail": "text"
},
"genericActionLogData": {
"additionalContext": "text"
}
}
}
}
],
"nextPageToken": "text"
}Fetch the activity history for a specific asset
The ID of the asset to fetch activities for
Unix timestamp in seconds, filters activities created ≥ the value
Unix timestamp in seconds, filters activities created < the value
Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/asset/activity?assetID=text&rangeStart=1&rangeEnd=1 HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"type": "DOWNLOAD",
"userEmail": "[email protected]",
"eventTime": 1,
"assetNames": [
"text"
],
"metadata": {
"downloadEventMetadata": {
"source": "text",
"fileName": "text"
},
"browserUploadMetadata": {
"domain": "text",
"fileName": "text"
},
"cloudSyncMetadata": {
"cloudApp": "text",
"fileName": "text"
},
"clipboardMetadata": {
"browserMetadata": {
"domain": "text"
}
}
}
}
],
"nextPageToken": "text"
}Fetch the activity history for a specific actor
The Nightfall ID of the actor to fetch activities for
Unix timestamp in seconds, filters activities created ≥ the value
Unix timestamp in seconds, filters activities created < the value
Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /exfiltration/v1/actor/activity?actorID=text&rangeStart=1&rangeEnd=1 HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"type": "DOWNLOAD",
"userEmail": "[email protected]",
"eventTime": 1,
"assetNames": [
"text"
],
"metadata": {
"downloadEventMetadata": {
"source": "text",
"fileName": "text"
},
"browserUploadMetadata": {
"domain": "text",
"fileName": "text"
},
"cloudSyncMetadata": {
"cloudApp": "text",
"fileName": "text"
},
"clipboardMetadata": {
"browserMetadata": {
"domain": "text"
}
}
}
}
],
"nextPageToken": "text"
}Fetch a list of violations for a period
Unix timestamp in seconds, filters records created ≥ the value, defaults to -90 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/violations HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"violations": [
{
"id": "text",
"integration": "SLACK",
"createdAt": 1,
"updatedAt": 1,
"possibleActions": [
"ACKNOWLEDGE"
],
"state": "ACTIVE",
"resourceLink": "text",
"metadata": {
"slackMetadata": {
"location": "text",
"locationType": "text",
"username": "text",
"userID": "text",
"messagePermalink": "text",
"locationMembers": [
"text"
],
"locationMemberCount": 1,
"channelID": "text",
"workspaceName": "text"
},
"confluenceMetadata": {
"itemName": "text",
"itemType": "text",
"isArchived": true,
"createdAt": 1,
"updatedAt": 1,
"labels": [
"text"
],
"spaceName": "text",
"spaceKey": "text",
"spaceNameLink": "text",
"parentPageName": "text",
"authorName": "text",
"authorEmail": "text",
"authorNameLink": "text",
"permalink": "text",
"confluenceID": "text",
"confluenceUserID": "text",
"itemVersion": 1,
"parentPageID": "text",
"parentVersion": 1
},
"gdriveMetadata": {
"fileID": "text",
"fileName": "text",
"fileType": "text",
"fileSize": "text",
"fileLink": "text",
"permissionSetting": "text",
"sharingExternalUsers": [
"text"
],
"sharingInternalUsers": [
"text"
],
"canViewersDownload": true,
"fileOwner": "text",
"isInTrash": true,
"createdAt": 1,
"updatedAt": 1,
"drive": "text",
"updatedBy": "text"
},
"jiraMetadata": {
"projectName": "text",
"ticketNumber": "text",
"projectType": "text",
"issueID": "text",
"projectLink": "text",
"ticketLink": "text",
"commentLink": "text",
"attachmentLink": "text"
},
"githubMetadata": {
"branchName": "text",
"organization": "text",
"repository": "text",
"authorEmail": "text",
"authorUsername": "text",
"createdAt": 1,
"isRepoPrivate": true,
"filePath": "text",
"githubPermalink": "text",
"repositoryOwner": "text",
"githubRepoLink": "text"
},
"salesforceMetadata": {
"orgName": "text",
"recordID": "text",
"objectName": "text",
"contentType": "text",
"userID": "text",
"userName": "text",
"updatedAt": 1,
"fields": [
"text"
],
"fileType": "text",
"attachmentLink": "text",
"attachmentName": "text",
"objectLink": "text"
},
"zendeskMetadata": {
"ticketStatus": "text",
"ticketTitle": "text",
"ticketRequestor": "text",
"ticketGroupAssignee": "text",
"ticketAgentAssignee": "text",
"currentUserRole": "text",
"ticketID": 1,
"ticketFollowers": [
"text"
],
"ticketTags": "text",
"createdAt": 1,
"UpdatedAt": 1,
"location": "text",
"subLocation": "text",
"ticketCommentID": 1,
"ticketGroupID": 1,
"ticketGroupLink": "text",
"ticketAgentID": 1,
"ticketAgentLink": "text",
"ticketEvent": "text",
"userRole": "text",
"attachmentName": "text",
"attachmentLink": "text"
},
"notionMetadata": {
"createdBy": "text",
"updatedBy": "text",
"workspaceName": "text",
"workspaceLink": "text",
"pageID": "text",
"pageTitle": "text",
"createdAt": 1,
"updatedAt": 1,
"privatePageLink": "text",
"publicPageLink": "text",
"sharedExternally": true,
"attachmentID": "text"
},
"browserMetadata": {
"location": "text",
"subLocation": "text",
"browserName": "text",
"userComment": "text"
},
"m365TeamsMetadata": {
"teamName": "text",
"tenantID": "text",
"tenantDomain": "text",
"teamID": "text",
"teamVisibility": "text",
"teamWebURL": "text",
"channelID": "text",
"channelName": "text",
"channelType": "text",
"channelWebURL": "text",
"messageID": "text",
"createdAt": 1,
"updatedAt": 1,
"chatMessageSender": "text",
"userID": "text",
"userPrincipalName": "text",
"attachments": [
{
"attachmentID": "text",
"attachmentName": "text",
"attachmentURL": "text"
}
],
"chatMessageImportance": "text",
"chatID": "text",
"chatType": "text",
"chatTopic": "text",
"chatParticipants": [
{
"userID": "text",
"email": "text",
"displayName": "text"
}
]
},
"m365OnedriveMetadata": {
"tenantID": "text",
"tenantDomain": "text",
"driveItemID": "text",
"driveItemName": "text",
"driveItemURL": "text",
"driveItemMimeType": "text",
"driveItemSize": 1,
"parentPath": "text",
"createdByID": "text",
"updatedByEmail": "text",
"updatedByID": "text",
"updatedByName": "text",
"createdAt": 1,
"updatedAt": 1,
"specialFolderName": "text",
"driveID": "text",
"driveOwnerName": "text",
"driveOwnerEmail": "text",
"driveOwnerID": "text"
},
"inlineEmailMetadata": {
"domain": "text",
"user_name": "text",
"from": "text",
"to": [
"text"
],
"cc": [
"text"
],
"bcc": [
"text"
],
"subject": "text",
"sent_at": 1,
"thread_id": "text",
"attachment_name": "text",
"attachment_type": "text"
}
},
"fileDetails": {
"fileName": "text",
"mimeType": "text",
"permalink": "text"
},
"policyUUIDs": [
"text"
],
"detectionRuleUUIDs": [
"text"
],
"detectorUUIDs": [
"text"
],
"risk": "UNSPECIFIED",
"riskSource": "NIGHTFALL",
"riskScore": 1,
"userInfo": {
"username": "text",
"userEmail": "text"
}
}
],
"nextPageToken": "text"
}Fetch a violation by ID
The UUID of the violation to fetch
Successful response
Invalid request parameters
Authentication failure
Violation does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/violations/{violationId} HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"id": "text",
"integration": "SLACK",
"createdAt": 1,
"updatedAt": 1,
"possibleActions": [
"ACKNOWLEDGE"
],
"state": "ACTIVE",
"resourceLink": "text",
"metadata": {
"slackMetadata": {
"location": "text",
"locationType": "text",
"username": "text",
"userID": "text",
"messagePermalink": "text",
"locationMembers": [
"text"
],
"locationMemberCount": 1,
"channelID": "text",
"workspaceName": "text"
},
"confluenceMetadata": {
"itemName": "text",
"itemType": "text",
"isArchived": true,
"createdAt": 1,
"updatedAt": 1,
"labels": [
"text"
],
"spaceName": "text",
"spaceKey": "text",
"spaceNameLink": "text",
"parentPageName": "text",
"authorName": "text",
"authorEmail": "text",
"authorNameLink": "text",
"permalink": "text",
"confluenceID": "text",
"confluenceUserID": "text",
"itemVersion": 1,
"parentPageID": "text",
"parentVersion": 1
},
"gdriveMetadata": {
"fileID": "text",
"fileName": "text",
"fileType": "text",
"fileSize": "text",
"fileLink": "text",
"permissionSetting": "text",
"sharingExternalUsers": [
"text"
],
"sharingInternalUsers": [
"text"
],
"canViewersDownload": true,
"fileOwner": "text",
"isInTrash": true,
"createdAt": 1,
"updatedAt": 1,
"drive": "text",
"updatedBy": "text"
},
"jiraMetadata": {
"projectName": "text",
"ticketNumber": "text",
"projectType": "text",
"issueID": "text",
"projectLink": "text",
"ticketLink": "text",
"commentLink": "text",
"attachmentLink": "text"
},
"githubMetadata": {
"branchName": "text",
"organization": "text",
"repository": "text",
"authorEmail": "text",
"authorUsername": "text",
"createdAt": 1,
"isRepoPrivate": true,
"filePath": "text",
"githubPermalink": "text",
"repositoryOwner": "text",
"githubRepoLink": "text"
},
"salesforceMetadata": {
"orgName": "text",
"recordID": "text",
"objectName": "text",
"contentType": "text",
"userID": "text",
"userName": "text",
"updatedAt": 1,
"fields": [
"text"
],
"fileType": "text",
"attachmentLink": "text",
"attachmentName": "text",
"objectLink": "text"
},
"zendeskMetadata": {
"ticketStatus": "text",
"ticketTitle": "text",
"ticketRequestor": "text",
"ticketGroupAssignee": "text",
"ticketAgentAssignee": "text",
"currentUserRole": "text",
"ticketID": 1,
"ticketFollowers": [
"text"
],
"ticketTags": "text",
"createdAt": 1,
"UpdatedAt": 1,
"location": "text",
"subLocation": "text",
"ticketCommentID": 1,
"ticketGroupID": 1,
"ticketGroupLink": "text",
"ticketAgentID": 1,
"ticketAgentLink": "text",
"ticketEvent": "text",
"userRole": "text",
"attachmentName": "text",
"attachmentLink": "text"
},
"notionMetadata": {
"createdBy": "text",
"updatedBy": "text",
"workspaceName": "text",
"workspaceLink": "text",
"pageID": "text",
"pageTitle": "text",
"createdAt": 1,
"updatedAt": 1,
"privatePageLink": "text",
"publicPageLink": "text",
"sharedExternally": true,
"attachmentID": "text"
},
"browserMetadata": {
"location": "text",
"subLocation": "text",
"browserName": "text",
"userComment": "text"
},
"m365TeamsMetadata": {
"teamName": "text",
"tenantID": "text",
"tenantDomain": "text",
"teamID": "text",
"teamVisibility": "text",
"teamWebURL": "text",
"channelID": "text",
"channelName": "text",
"channelType": "text",
"channelWebURL": "text",
"messageID": "text",
"createdAt": 1,
"updatedAt": 1,
"chatMessageSender": "text",
"userID": "text",
"userPrincipalName": "text",
"attachments": [
{
"attachmentID": "text",
"attachmentName": "text",
"attachmentURL": "text"
}
],
"chatMessageImportance": "text",
"chatID": "text",
"chatType": "text",
"chatTopic": "text",
"chatParticipants": [
{
"userID": "text",
"email": "text",
"displayName": "text"
}
]
},
"m365OnedriveMetadata": {
"tenantID": "text",
"tenantDomain": "text",
"driveItemID": "text",
"driveItemName": "text",
"driveItemURL": "text",
"driveItemMimeType": "text",
"driveItemSize": 1,
"parentPath": "text",
"createdByID": "text",
"updatedByEmail": "text",
"updatedByID": "text",
"updatedByName": "text",
"createdAt": 1,
"updatedAt": 1,
"specialFolderName": "text",
"driveID": "text",
"driveOwnerName": "text",
"driveOwnerEmail": "text",
"driveOwnerID": "text"
},
"inlineEmailMetadata": {
"domain": "text",
"user_name": "text",
"from": "text",
"to": [
"text"
],
"cc": [
"text"
],
"bcc": [
"text"
],
"subject": "text",
"sent_at": 1,
"thread_id": "text",
"attachment_name": "text",
"attachment_type": "text"
}
},
"fileDetails": {
"fileName": "text",
"mimeType": "text",
"permalink": "text"
},
"policyUUIDs": [
"text"
],
"detectionRuleUUIDs": [
"text"
],
"detectorUUIDs": [
"text"
],
"risk": "UNSPECIFIED",
"riskSource": "NIGHTFALL",
"riskScore": 1,
"userInfo": {
"username": "text",
"userEmail": "text"
}
}Fetch a list of violations based on some filters
Unix timestamp in seconds, filters records created ≥ the value, defaults to -90 days UTC
Unix timestamp in seconds, filters records created < the value, defaults to end of the current day UTC
Unix timestamp in seconds, filters records updated > the value
The maximum number of records to be returned in the response
50Cursor for getting the next page of results
Sort key and direction, defaults to descending order by creation time
TIME_DESCPossible values: The query containing filter clauses
Query structure and terminology
A query clause consists of a field followed by an operator followed by a value:
| term | value |
|---|---|
| clause | user_email:"[email protected]" |
| field | user_email |
| operator | : |
| value | [email protected] |
You can combine multiple query clauses in a search by separating them with a space.
Field types, substring matching, and numeric comparators
Every search field supports exact matching with a :. Certain fields such as user_email and user_name support substring matching.
Quotes
You may use quotation marks around string values. Quotation marks are required in case the value contains spaces. For example:
user_mail:[email protected]user_name:"John Doe"Special Characters
+ - && || ! ( ) { } [ ] ^ " ~ * ? : are special characters need to be escaped using \. For example:
(1+1):2 should be searched for using \(1\+1)\:2Search Syntax
The following table lists the syntax that you can use to construct a query.
| SYNTAX | USAGE | DESCRIPTION | EXAMPLES |
|---|---|---|---|
: |
field:value | Exact match operator (case insensitive) | state:"pending" returns records where the currency is exactly "PENDING" in a case-insensitive comparison |
(space) |
field1:value1 field2:value2 | The query returns only records that match both clauses | state:active slack.channel_name:general |
OR |
field:(value1 OR value2) | The query returns records that match either of the values (case insensitive) | state:(active OR pending) |
Query Fields
| param | description |
|---|---|
| state | the violation states to filter on |
| user_email | the emails of users updating the resource resulting in the violation |
| user_name | the usernames of users updating the resource resulting in the violation |
| integration_name | the integration to filter on |
| confidence | one or more likelihoods/confidences |
| policy_id | one or more policy IDs |
| detection_rule_id | one or more detection rule IDs |
| detector_id | one or more detector IDs |
| risk_label | the risk label to filter on |
| risk_source | the risk determination source to filter on |
| slack.channel_name | the slack channel names to filter on |
| slack.channel_id | the slack channel IDs to filter on |
| slack.workspace | the slack workspaces to filter on |
| confluence.parent_page_name | the names of the parent pages in confluence to filter on |
| confluence.space_name | the names of the spaces in confluence to filter on |
| gdrive.drive | the drive names in gdrive to filter on |
| jira.project_name | the jira project names to filter on |
| jira.ticket_number | the jira ticket numbers to filter on |
| salesforce.org_name | the salesforce organization names to filter on |
| salesforce.object | the salesforce object names to filter on |
| salesforce.record_id | the salesforce record IDs to filter on |
| github.author_email | the github author emails to filter on |
| github.branch | the github branches to filter on |
| github.commit | the github commit ids to filter on |
| github.org | the github organizations to filter on |
| github.repository | the github repositories to filter on |
| github.repository_owner | the github repository owners to filter on |
| teams.team_name | the m365 teams team names to filter on |
| teams.channel_name | the m365 teams channels to filter on |
| teams.channel_type | the m365 teams channel types to filter on |
| teams.team_sensitivity | the m365 teams sensitivities to filter on |
| teams.sender | the m365 teams senders to filter on |
| teams.msg_importance | the m365 teams importance to filter on |
| teams.msg_attachment | the m365 teams attachment names to filter on |
| teams.chat_id | the m365 teams chat ID to filter on |
| teams.chat_type | the m365 teams chat type to filter on |
| teams.chat_topic | the m365 teams chat topic to filter on |
| teams.chat_participant | the m365 teams chat participant's display name to filter on |
| onedrive.drive_owner | drive owner's display name to filter on |
| onedrive.drive_owner_email | drive owner's email to filter on |
| onedrive.file_name | the file name to filter on |
| onedrive.created_by | the m365 user, who created the file in the drive, display name to filter on |
| onedrive.created_by_email | the m365 users, who created the file in the drive, email to filter on |
| onedrive.modified_by | the m365 users, who last modified the file in the drive, display name to filter on |
| onedrive.modified_by_email | the m365 users, who last modified the file in the drive, email to filter on |
| zendesk.ticket_status | the zendesk ticket status to filter on |
| zendesk.ticket_title | the zendesk ticket titles to filter on |
| zendesk.ticket_group_assignee | the zendesk ticket assignee groups to filter on |
| zendesk.current_user_role | the zendesk ticket current assignee user's roles to filter on |
| notion.created_by | the names of the users creating a resource in notion to filter on |
| notion.last_edited_by | the names of the users editing a resource in notion to filter on |
| notion.page_title | the page names in notion to filter on |
| notion.workspace_name | the workspace names in notion to filter on |
| gmail.user_name | the names of the sender to filter on |
| gmail.from | the email of sender to filter on |
| gmail.to | the email or name of recipients to filter on |
| gmail.cc | the email or name of cc to filter on |
| gmail.bcc | the email or name of bcc to filter on |
| gmail.thread_id | the thread id of email to filter on |
| gmail.subject | the subject of email to filter on |
| gmail.attachment_name | the name of attachment to filter on |
| gmail.attachment_type | the type of attachment to filter on |
| last_actioned_by | the entity that performed the last action on the violation, can be one of NIGHTFALL, ADMIN or END_USER |
Successful response
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/violations/search?query=text HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"violations": [
{
"id": "text",
"integration": "SLACK",
"createdAt": 1,
"updatedAt": 1,
"possibleActions": [
"ACKNOWLEDGE"
],
"state": "ACTIVE",
"resourceLink": "text",
"metadata": {
"slackMetadata": {
"location": "text",
"locationType": "text",
"username": "text",
"userID": "text",
"messagePermalink": "text",
"locationMembers": [
"text"
],
"locationMemberCount": 1,
"channelID": "text",
"workspaceName": "text"
},
"confluenceMetadata": {
"itemName": "text",
"itemType": "text",
"isArchived": true,
"createdAt": 1,
"updatedAt": 1,
"labels": [
"text"
],
"spaceName": "text",
"spaceKey": "text",
"spaceNameLink": "text",
"parentPageName": "text",
"authorName": "text",
"authorEmail": "text",
"authorNameLink": "text",
"permalink": "text",
"confluenceID": "text",
"confluenceUserID": "text",
"itemVersion": 1,
"parentPageID": "text",
"parentVersion": 1
},
"gdriveMetadata": {
"fileID": "text",
"fileName": "text",
"fileType": "text",
"fileSize": "text",
"fileLink": "text",
"permissionSetting": "text",
"sharingExternalUsers": [
"text"
],
"sharingInternalUsers": [
"text"
],
"canViewersDownload": true,
"fileOwner": "text",
"isInTrash": true,
"createdAt": 1,
"updatedAt": 1,
"drive": "text",
"updatedBy": "text"
},
"jiraMetadata": {
"projectName": "text",
"ticketNumber": "text",
"projectType": "text",
"issueID": "text",
"projectLink": "text",
"ticketLink": "text",
"commentLink": "text",
"attachmentLink": "text"
},
"githubMetadata": {
"branchName": "text",
"organization": "text",
"repository": "text",
"authorEmail": "text",
"authorUsername": "text",
"createdAt": 1,
"isRepoPrivate": true,
"filePath": "text",
"githubPermalink": "text",
"repositoryOwner": "text",
"githubRepoLink": "text"
},
"salesforceMetadata": {
"orgName": "text",
"recordID": "text",
"objectName": "text",
"contentType": "text",
"userID": "text",
"userName": "text",
"updatedAt": 1,
"fields": [
"text"
],
"fileType": "text",
"attachmentLink": "text",
"attachmentName": "text",
"objectLink": "text"
},
"zendeskMetadata": {
"ticketStatus": "text",
"ticketTitle": "text",
"ticketRequestor": "text",
"ticketGroupAssignee": "text",
"ticketAgentAssignee": "text",
"currentUserRole": "text",
"ticketID": 1,
"ticketFollowers": [
"text"
],
"ticketTags": "text",
"createdAt": 1,
"UpdatedAt": 1,
"location": "text",
"subLocation": "text",
"ticketCommentID": 1,
"ticketGroupID": 1,
"ticketGroupLink": "text",
"ticketAgentID": 1,
"ticketAgentLink": "text",
"ticketEvent": "text",
"userRole": "text",
"attachmentName": "text",
"attachmentLink": "text"
},
"notionMetadata": {
"createdBy": "text",
"updatedBy": "text",
"workspaceName": "text",
"workspaceLink": "text",
"pageID": "text",
"pageTitle": "text",
"createdAt": 1,
"updatedAt": 1,
"privatePageLink": "text",
"publicPageLink": "text",
"sharedExternally": true,
"attachmentID": "text"
},
"browserMetadata": {
"location": "text",
"subLocation": "text",
"browserName": "text",
"userComment": "text"
},
"m365TeamsMetadata": {
"teamName": "text",
"tenantID": "text",
"tenantDomain": "text",
"teamID": "text",
"teamVisibility": "text",
"teamWebURL": "text",
"channelID": "text",
"channelName": "text",
"channelType": "text",
"channelWebURL": "text",
"messageID": "text",
"createdAt": 1,
"updatedAt": 1,
"chatMessageSender": "text",
"userID": "text",
"userPrincipalName": "text",
"attachments": [
{
"attachmentID": "text",
"attachmentName": "text",
"attachmentURL": "text"
}
],
"chatMessageImportance": "text",
"chatID": "text",
"chatType": "text",
"chatTopic": "text",
"chatParticipants": [
{
"userID": "text",
"email": "text",
"displayName": "text"
}
]
},
"m365OnedriveMetadata": {
"tenantID": "text",
"tenantDomain": "text",
"driveItemID": "text",
"driveItemName": "text",
"driveItemURL": "text",
"driveItemMimeType": "text",
"driveItemSize": 1,
"parentPath": "text",
"createdByID": "text",
"updatedByEmail": "text",
"updatedByID": "text",
"updatedByName": "text",
"createdAt": 1,
"updatedAt": 1,
"specialFolderName": "text",
"driveID": "text",
"driveOwnerName": "text",
"driveOwnerEmail": "text",
"driveOwnerID": "text"
},
"inlineEmailMetadata": {
"domain": "text",
"user_name": "text",
"from": "text",
"to": [
"text"
],
"cc": [
"text"
],
"bcc": [
"text"
],
"subject": "text",
"sent_at": 1,
"thread_id": "text",
"attachment_name": "text",
"attachment_type": "text"
}
},
"fileDetails": {
"fileName": "text",
"mimeType": "text",
"permalink": "text"
},
"policyUUIDs": [
"text"
],
"detectionRuleUUIDs": [
"text"
],
"detectorUUIDs": [
"text"
],
"risk": "UNSPECIFIED",
"riskSource": "NIGHTFALL",
"riskScore": 1,
"userInfo": {
"username": "text",
"userEmail": "text"
}
}
],
"nextPageToken": "text"
}Get findings for a specific violation
The UUID of the violation
Cursor for getting the next page of results
Number of findings to fetch in one page (max 1000)
1000Successful response
Invalid request parameters
Authentication failure
Violation does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/violations/{violationId}/findings HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"findings": [
{
"id": "text",
"detectorUUID": "text",
"subDetectorUUID": "text",
"confidence": "text",
"redactedSensitiveText": "text",
"redactedContext": {
"beforeContext": "text",
"afterContext": "text"
},
"redactedLocation": {
"byteRange": {
"start": 1,
"end": 1
},
"lineRange": {
"start": 1,
"end": 1
}
},
"metadata": {
"apiKeyMetaData": {
"status": "UNVERIFIED",
"kind": "UNSPECIFIED",
"description": "text"
}
},
"subLocation": "text",
"annotationUUID": "text"
}
],
"nextPageToken": "text"
}Perform an action on a list of violations. If an action can't be performed on a violation, that violation is ignored. Depending on the action, it could be processed immediately or queued.
The UUIDs of the violations to perform the action on
The action to perform on the violations
Successful response (processed immediately)
Accepted response (queued for processing)
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
POST /dlp/v1/violations/actions HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 82
{
"violationUUIDs": [
"123e4567-e89b-12d3-a456-426614174000"
],
"action": "ACKNOWLEDGE"
}{
"submitted": [
"123e4567-e89b-12d3-a456-426614174000"
]
}Fetch an annotation by ID
The UUID of the annotation to fetch
Successful response
Invalid request parameters
Authentication failure
Annotation does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/annotations/{annotationId} HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"type": "DETECTOR_FALSE_POSITIVE",
"comment": "text",
"autoApply": true
}Annotate a finding
The UUID of the finding to annotate
The annotation type
The comment to add to the annotation
Whether the annotation applies to all findings of this sensitive data (defaults to true)
trueSuccessful response
Invalid request parameters
Authentication failure
Finding already annotated
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
POST /dlp/v1/findings/{findingId}/annotate HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 68
{
"type": "DETECTOR_FALSE_POSITIVE",
"comment": "text",
"autoApply": true
}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"type": "DETECTOR_FALSE_POSITIVE",
"comment": "text",
"autoApply": true
}Remove the annotation for a finding
The UUID of the finding to unannotate
Successful response (even if annotation does not exist)
Invalid request parameters
Authentication failure
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
POST /dlp/v1/findings/{findingId}/unannotate HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
No content
Fetch the activity feed for a specific violation
The UUID of the violation
Number of activity items to fetch in one page
50Unix timestamp in seconds, filters activity created > the value
Unix timestamp in seconds, filters activity created < the value
Whether to sort results in descending order (default false)
falseCursor for getting the next page of results
Successful response
Invalid request parameters
Authentication failure
Violation does not exist
Rate Limit Exceeded or Daily Quota Exceeded
Internal Nightfall Error
GET /dlp/v1/violations/{violationId}/activity HTTP/1.1
Host: api.nightfall.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"activities": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"message": "text",
"timestamp": 1,
"type": "CREATION",
"data": {
"action": "ACKNOWLEDGE",
"userUUID": "123e4567-e89b-12d3-a456-426614174000",
"userName": "text",
"userEmail": "text",
"receiverEmail": "text",
"actionLogData": {
"applyLabelsActionLogData": {
"activityType": "LABELS_SUCCESSFULLY_APPLIED",
"labels": [
"text"
],
"labelUpdatesOnResource": {
"ANY_ADDITIONAL_PROPERTY": "text"
},
"failureReason": "text"
},
"setExpirationActionLogData": {
"expirationTime": 1
},
"revokeAccessActionLogData": {
"revokedEmails": [
"text"
],
"unRevokedEmails": [
"text"
]
},
"notifyEmailActionLogData": {
"receiverEmail": "text"
},
"genericActionLogData": {
"additionalContext": "text"
}
}
}
}
],
"nextPageToken": "text"
}