← All KB Checks
CRITICALLinterCheckDeferred to v1.1

Hardcoded Credentials Detector

nowisor-hardcoded-credentials··Source on GitHub →

Hardcoded Credentials Detector

Status: Deferred to nowisor v1.1. This check ships in the v1.0.0 agent pack with active: false. It does not execute against your instance and produces no findings. The v1.1 release will reactivate it with a redesigned detection predicate. Until then, follow the manual workaround below to assess this risk class.

What this check will detect (when reactivated)

The nowisor-hardcoded-credentials check is a LinterCheck that walks every server-side script in your instance (Script Includes, Business Rules, UI Actions, scheduled scripts) and flags string literals that match credential-shaped patterns:

When active, the check produces one finding per detected pattern, including the source script and line number. The finding's metadata includes a redacted indicator (no credential value in the finding body — only confirmation that a credential-shaped string was detected at that location).

Credentials in script source code are extractable by anyone with read access to the script table, persist across instance upgrades, and propagate to sub-production environments via clone. Every match is an exposure path that bypasses the platform's purpose-built credential storage (Credential records, Credential Vault Provider).

Why this check is deferred

During Tier 2 verification (2026-05-12), planted-artifact testing on dev265484 (Zurich Patch 6) revealed that the v1.0.0 predicate produces zero findings even when a script clearly contains a hardcoded credential. The root cause is in the AST representation:

The predicate looks for a string literal whose value matches the labelled-assignment regex (e.g., password\s*[:=]\s*['"]…['"]). But the platform's AST splits a statement like var password = "Pa$$w0rd"; into three separate nodes — a NAME node containing the identifier password, an operator node, and a LITERAL node containing only the string Pa$$w0rd. The literal's value, taken alone, never contains the password = label that the regex looks for.

The fix is a predicate rewrite: detect a NAME node whose identifier matches the credential-name pattern, then check that an adjacent LITERAL node contains a high-entropy string value. v1.1 will ship this rewrite plus planted-artifact verification before the check is reactivated.

We chose to ship v1.0.0 with the check disabled rather than ship it producing false-negatives. A check that silently misses real exposures is more dangerous than no check at all — it creates a false sense of coverage.

Manual workaround until v1.1

Until v1.1 ships, you can run the manual audit below as a Background Script. It uses a SQL-style content scan against script tables rather than AST parsing, so it produces some false positives (script names containing the word "password," comments discussing password requirements) but it does not miss real hardcoded credentials the way the v1.0 AST predicate does.

/*
 * Manual workaround for nowisor-hardcoded-credentials (v1.0.0 deferred)
 * Read-only, safe for production.
 * Pattern-scans script-bearing tables for credential-shaped content.
 * Review each result manually — false positives are expected.
 */
(function manualScan() {
    var tables = [
        'sys_script',
        'sys_script_include',
        'sys_script_client',
        'sysauto_script',
        'sys_processor',
        'sys_ws_operation',
    ];

    var found = 0;
    for (var t = 0; t < tables.length; t++) {
        var gr = new GlideRecord(tables[t]);
        gr.addQuery('active', true);
        gr.addEncodedQuery(
            'scriptLIKEpassword=^ORscriptLIKEpassword :' +
                '^ORscriptLIKEapi_key=^ORscriptLIKEapiKey=' +
                '^ORscriptLIKEsecret=^ORscriptLIKEtoken='
        );
        gr.query();
        while (gr.next()) {
            found++;
            gs.print(
                tables[t] +
                    ': ' +
                    gr.getValue('name') +
                    ' (sys_id: ' +
                    gr.getValue('sys_id') +
                    ')'
            );
        }
    }
    gs.print('');
    gs.print('Total candidate scripts: ' + found);
    gs.print(
        'Review each manually for actual hardcoded credentials. ' +
            'Move real credentials to Credential records or Credential Vault.'
    );
})();

Review every result. For each script that contains a real hardcoded credential:

  1. Move the credential to a Credential record (discovery_credentials, jdbc_credentials, basic_auth_credentials — match the credential type) or to the Credential Vault Provider for high-sensitivity tokens.
  2. Replace the inline string with the appropriate retrieval API call.
  3. Rotate the credential — assume the inline value is compromised the moment it left your instance via update set, clone, or export.

What to do next

This is one of two checks deferred to v1.1 — see nowisor-direct-property-write for the other. The pack ships 49 checks, 47 of them active. The 2 deferred checks have explicit reactivation criteria documented in source and manifest, and v1.1 will reactivate them after the planted-artifact gate clears.

The nowisor advisor product is being built to track deferred checks and notifies subscribers when reactivation lands — so when v1.1 ships, you'll know to re-run the agent pack with the credential detector enabled. Learn more at nowisor.com.

For now, run the manual audit above as a quarterly task and document the results as part of your code-review evidence package.