// TECHNOLOGIES / BOX

Box

Box is secure. Your integration is the question.

The Box Admin Console reports every shared link in your enterprise and Box Shield watches how content moves. Neither one can see the application your developers built on top of the API — and that is where the whole attack surface of a Box integration lives. NewScan scans it from the outside, with no Box credential and no access to your tenant: only your own app, from the seat an attacker actually occupies.

The half of it Box can't audit

// why an external scan finds different things

Box's own controls are good at what they can see: the link inventory, content classification, the access event stream, and an admin gate on every server-auth app. All of that is Box-side state. None of it reaches the config file your deploy serves at its web root, the token your page hands to a browser, the webhook receiver that never checks a signature, or the API route that forwards a query-string user id into As-User. Those are written by your team, run on your infrastructure, and are invisible to the console by construction.

// BOX SEES THIS

Which links exist and who they're shared with. Which apps are authorised and with what scopes. Who downloaded what, and whether that looked anomalous. Content classification and retention.

// NOBODY SEES THIS

A config.json with the app's private key sitting under the web root. A client secret compiled into the bundle. A dependency whose signature validator leaks timing. An endpoint that takes the user id from the caller. Box never gets a request about any of it.

The file that is your application

// tool: probe_exposure_paths

Box's JWT server-auth flow hands you exactly one file, and tells you it is the only chance you get to save the private key. That is precisely why it ends up committed next to the code and deployed with it. Anyone holding it mints a JWT assertion, exchanges it for a Service Account token, and reaches everything the app can reach — and if the app was authorised with enterprise access, that is the enterprise's content. Rotating a leaked one means a new key pair in the Developer Console and re-authorisation in the Admin Console.

// WHAT'S IN IT

{"boxAppSettings": { "clientID": "…", "clientSecret": "…", "appAuth": { "publicKeyID": "…", "privateKey": "-----BEGIN ENCRYPTED…", "passphrase": "…" }}, "enterpriseID": "…"}

// WHERE IT'S PROBED

/config.json /box_config.json /box-config.json /boxconfig.json /box_jwt_config.json /config/box.json /box-app-settings.json /.box/config.json

Plus anywhere else the pair turns up — a JS bundle, an API response, a debug page. The signature is content, not location, so it attributes wherever it lands.

// WHY /CONFIG.JSON IS SAFE TO PROBE

config.json is one of the most common filenames on the web, and probing it would be worthless if a 200 were the finding. It isn't. The response has to carry boxAppSettings and appAuth — two product-specific JSON keys that no ordinary app config has. A generic config.json costs one request and records nothing.

The 32-character problem

// tool: scan_sensitive_data

Most cloud credentials announce themselves. An AWS key starts AKIA, a Supabase token sbp_, a Grafana token glsa_ — match the prefix and you're done. A Box client secret and a Box access token are 32 characters of undifferentiated letters and digits with no prefix at all, which is also an exact description of every build hash, cache key, ETag and asset fingerprint in a modern bundle. A scanner that flags the value flags your whole deploy.

So the value is never the signal. The name it was assigned to is — and the value only has to look like a real one rather than a placeholder.

// FLAGGED

BOX_CLIENT_SECRET=eeee5555ffff6666gggg7777hhhh8888 boxClientSecret: "eeee5555ffff…" {"box-client-secret": "eeee5555ffff…"} BOX_DEVELOPER_TOKEN=q7Zk2mFa9Lp1Rd4Ts8Yv0Xc6Bn3Hj5Kw boxApiToken=`q7Zk2mFa9Lp1…`

Client secret is critical — it never expires and it never downscopes. An access or developer token is high: still 60 minutes of the Service Account's content to anyone with the page, but it does expire, and it may have been downscoped. Box's own documentation says developer tokens are for development and testing, never production.

// NOT FLAGGED (ON PURPOSE)

const hash = "a1b2c3d4e5f60718293a4b5c6d7e8f90"; # a 32-char value with no Box name BOX_CLIENT_SECRET=YOUR_SECRET_HERE # right name, not a secret shape boxClientSecret: "xxxxxxxxxxxxxxxx…" boxClientSecret: "0000000000000000…" # placeholders: no letter+digit mix

Two conditions, both required: a Box credential name, and a 32-character value carrying at least one letter and one digit. That second test is what keeps the documentation examples and the redacted-config screenshots out of your report — with no exception list to maintain.

Impersonation, one header wide

// tools: test_header_trust · discover_hidden_params

A Box app with the right advanced features can act as any user in the enterprise — Box describes the Client Credentials Grant as letting an app "act on behalf of any user in an enterprise." That power is delegated through one header. The failure is not Box granting it; the failure is your app deciding who to be from something the caller controls.

// THE SHAPE THAT FAILS

# your API GET /api/files?box_user_id=10032 # your server, faithfully As-User: 10032 -> someone else's Box

// WHAT NEWSCAN SENDS

# baseline: denied GET /account -> 403 # same request + one header As-User: <an id the app disclosed> -> 200, different data

The ids aren't guessed. They're the ones the app itself handed out in an id-named form field, plus their immediate neighbours — so the probe is a real account, not a fishing expedition. box_user_id, box_subject_id and as_user also join the hidden-parameter sweep, which finds the ones your API accepts but never documents.

// A DIFFERENTIAL, NOT A GUESS

Nothing is recorded unless a request that was denied becomes one that succeeds when the header is added — and the flip has to reproduce. An app that ignores the header behaves identically both times and is never mentioned.

The webhook validator you didn't write

// tool: mine_versions

Box signs every webhook with HMAC-SHA256 over the body concatenated with the delivery timestamp, sends two signatures so keys can rotate, and tells implementers two things in writing: reject a payload older than ten minutes, and compare the digests with a timing-safe function. Almost nobody writes that comparison themselves — the SDK does it. Which is the problem.

Component What it is Fixed in Severity
box-node-sdk
no CVE assigned
Webhook signature validation used an ordinary string comparison rather than a constant-time one, so the digest check leaked a timing oracle. The SDK's own changelog entry for 3.8.1, dated 11 June 2025, is "Use constant-time comparison for HMAC signatures" — every integration still on an older release carries a validator with that flaw and did not write a line of it. 3.8.1 medium

It fires from a package.json or lockfile your deploy actually serves, matched on an exact pinned version — never a range, never a guess. The version comparison is semantic, so the 10.x line reads as newer than 3.8.1 rather than older, which a string comparison gets backwards.

// "BOX HAS NO CVES" IS NOT THE COMFORT IT SOUNDS LIKE

Box is SaaS, so its own bugs get fixed server-side and disclosed through HackerOne rather than landing in NVD with an identifier. The two account-takeover MFA bypasses researchers found in 2021 — mixing SMS and TOTP enrollment, and an unenrollment endpoint reachable before the second factor — were fixed years ago and never got a CVE number. A short CVE list doesn't mean a small attack surface; it means the surface moved into your integration, which is the entire premise of this page.

What it deliberately doesn't do

// the line, and why it's there

In March 2019, researchers enumerated Box enterprise subdomains and their shared links and found more than 90 companies exposing hundreds of thousands of documents — passport scans, bank details, network diagrams, VPN configs, years of internal meeting archives. Apple, Discovery, Amadeus and Box itself were in the set. That was not a Box vulnerability: Box enterprise content is private by default, and every one of those links had been set to public by someone. Box's own guidance since then is blunt about the mechanism — custom shared URLs "can be easily guessed by outside parties," and links posted on a website get indexed by search engines.

// WE WILL NOT ENUMERATE YOUR TENANT

No vanity-subdomain sweeps. No shared-link guessing. No brute-forcing custom /v/ URLs. That is the 2019 attack, and running it — even against your own tenant, even with your permission — produces a list of other people's documents alongside yours. A scanner that will do it for you will do it to you.

// AND WE WON'T SPEND WHAT WE FIND

A leaked key pair is never exchanged for a token to "confirm" it. A discovered access token is never sent to api.box.com. Possession is the finding — proving it works would mean us authenticating as your application, which is not a thing a scanner should ever do.

// NOT YET: PUBLIC LINKS YOUR APP PUBLISHES

Links your own pages and bundles point at are fair game and are in the plan — but a app.box.com/s/… URL alone doesn't prove the content is public, because a company-scoped link has exactly the same shape. That needs an unauthenticated fetch to separate the two, and until that ships, saying "this is public" would be a guess. So this page doesn't claim it. The full detection catalog, including that class, is written up in the scanner's own docs/tech/box.md.

The Box-side settings we can't test for you

// admin console, not the scan

These live inside your tenant, so no external scan can see them and nobody should tell you otherwise. They are the other half of a Box integration review, and they're short:

DEFAULT LINK ACCESS

Set to People in your company, not People with the link. Box has recommended this since 2019.

CUSTOM URLS

Public custom URLs off until an admin enables them. A random 32-character code isn't brute-forceable; q3-board-deck is.

LINK REPORTS

Run the shared-link report on a schedule, with expiry and password policies enforced. The inventory only helps if somebody reads it.

APP SCOPES

Review which apps hold enterprise_content, manage_managed_users, manage_enterprise_properties or Generate User Access Tokens. A preview integration needs none of them.

APP ACCESS ONLY

Server-auth apps set to App access only unless enterprise access is genuinely required. It is the difference between a blast radius of one folder and one of everything.

WEBHOOK SIGNATURES

Signature keys are opt-in per webhook. A webhook without one has a receiver that anyone on the internet can drive — and there is nothing to verify.

How it's recorded

// audit-ready evidence

CATEGORY

A leaked config or secret maps to CWE-798 (hard-coded credentials); the As-User class to CWE-639 (authorization bypass through a user-controlled key).

SEVERITY

One calibrated severity that translates into each framework's risk rating and remediation SLA — no inflated numbers, and a token is not filed as a private key.

EVIDENCE

Where it was found, what marked it, and the remediation that actually closes it — rotate the key pair and re-authorise the app, not just rotate. Credential values are redacted in the report.

Find out if you got your Box integration right.

NewScan is free and self-hosted — bring your own key. Everything on this page runs in the deterministic floor, with no model and no provider key at all, and it never touches your Box tenant.